k9guard 1.0.1 → 1.0.3

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 K9Crypt
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 CHANGED
@@ -4,15 +4,16 @@
4
4
 
5
5
  # K9Guard
6
6
 
7
- A secure, lightweight, and flexible CAPTCHA module for TypeScript/JavaScript projects with cryptographic security and multi-language support.
7
+ A secure, lightweight, and flexible CAPTCHA module for TypeScript/JavaScript projects with cryptographic security.
8
8
 
9
9
  ## Features
10
10
 
11
11
  - **Cryptographically Secure**: NIST SP 800-90A compliant random generation
12
- - **Multi-Language Support**: English and Turkish locales for riddles and logic questions
13
- - **9 CAPTCHA Types**: Math, text, riddle, sequence, scramble, logic, reverse, mixed, and multi-step challenges
14
- - **Security First**: SHA-256 salted hashing, nonce-based session management, and 5-minute expiry
15
- - **Input Validation**: Length limits, type checking, and sanitization to prevent injection attacks
12
+ - **10 CAPTCHA Types**: Math, text, sequence, scramble, reverse, mixed, multi-step, image, emoji, and custom challenges
13
+ - **Security First**: SHA-256 salted hashing, server-side challenge store, nonce-based session management, and 5-minute expiry
14
+ - **Single-Use Challenges**: Every nonce is consumed on the first `validate()` call — success or failure — preventing replay and brute-force attacks
15
+ - **Strict Configuration**: Invalid `type` or `difficulty` values throw immediately; no silent fallbacks
16
+ - **Input Validation**: Length limits, strict numeric parsing, type checking, and sanitization to prevent injection attacks
16
17
  - **Custom Questions**: Support for your own questions with validation and sanitization
17
18
  - **Zero Dependencies**: Lightweight with no external dependencies
18
19
  - **Well Tested**: Comprehensive test coverage including edge cases and security scenarios
@@ -32,8 +33,7 @@ import K9Guard from "k9guard";
32
33
 
33
34
  const captcha = new K9Guard({
34
35
  type: 'math',
35
- difficulty: 'medium',
36
- locale: 'en'
36
+ difficulty: 'medium'
37
37
  });
38
38
 
39
39
  // generate a challenge
@@ -69,15 +69,6 @@ const challenge = captcha.generate();
69
69
  // Answer: "aB2xY9"
70
70
  ```
71
71
 
72
- ### Riddle CAPTCHA
73
-
74
- ```typescript
75
- const captcha = new K9Guard({ type: 'riddle', difficulty: 'easy', locale: 'en' });
76
- const challenge = captcha.generate();
77
- // Output: "What has keys but can't open locks?"
78
- // Answer: "piano"
79
- ```
80
-
81
72
  ### Sequence CAPTCHA
82
73
 
83
74
  ```typescript
@@ -96,24 +87,73 @@ const challenge = captcha.generate();
96
87
  // Answer: "cat"
97
88
  ```
98
89
 
99
- ### Logic CAPTCHA
90
+ ### Reverse CAPTCHA
100
91
 
101
92
  ```typescript
102
- const captcha = new K9Guard({ type: 'logic', difficulty: 'easy', locale: 'en' });
93
+ const captcha = new K9Guard({ type: 'reverse', difficulty: 'easy' });
103
94
  const challenge = captcha.generate();
104
- // Output: "Water is dry. True or False?"
105
- // Answer: "false"
95
+ // Output: "god"
96
+ // Answer: "dog"
106
97
  ```
107
98
 
108
- ### Reverse CAPTCHA
99
+ ### Image CAPTCHA
109
100
 
110
101
  ```typescript
111
- const captcha = new K9Guard({ type: 'reverse', difficulty: 'easy' });
102
+ const captcha = new K9Guard({ type: 'image', difficulty: 'medium' });
112
103
  const challenge = captcha.generate();
113
- // Output: "god"
114
- // Answer: "dog"
104
+
105
+ // challenge.image — base64 SVG data URI, render it directly in an <img> tag
106
+ // challenge.question — "Type the characters shown in the image"
107
+ console.log(challenge.image); // "data:image/svg+xml;base64,..."
108
+
109
+ // validate user input (case-insensitive)
110
+ const isValid = captcha.validate(challenge, "aB3z");
111
+ if (isValid) {
112
+ console.log("Access granted!");
113
+ } else {
114
+ console.log("Wrong answer!");
115
+ }
116
+ ```
117
+
118
+ The image is a distorted SVG with:
119
+ - **Rotated & offset characters** per-glyph, randomized color and size
120
+ - **Sinusoidal wave overlays** proportional to difficulty
121
+ - **Noise lines and dots** that break simple segmentation attacks
122
+ - **Case-insensitive validation** — user may type upper or lowercase
123
+ - **No external dependencies** — pure SVG generated server-side
124
+
125
+ ### Emoji CAPTCHA
126
+
127
+ ```typescript
128
+ const captcha = new K9Guard({ type: 'emoji', difficulty: 'medium' });
129
+ const challenge = captcha.generate();
130
+
131
+ // challenge.emojis — array of emojis to display (6 for medium)
132
+ // challenge.category — the target category name (e.g. "animals")
133
+ // challenge.question — "Select all animals from the list (6 emojis, 3 correct)"
134
+ console.log(challenge.emojis); // ["🐶", "🍎", "🚗", "🐱", "🌸", "🏀"]
135
+ console.log(challenge.category); // "animals"
136
+
137
+ // user submits sorted comma-separated zero-based indices of the correct emojis
138
+ // e.g. if emojis[0] and emojis[3] are animals: "0,3"
139
+ const isValid = captcha.validate(challenge, "0,3");
140
+ if (isValid) {
141
+ console.log("Access granted!");
142
+ } else {
143
+ console.log("Wrong answer!");
144
+ }
115
145
  ```
116
146
 
147
+ Difficulty controls the number of emojis shown and correct answers required:
148
+
149
+ | Difficulty | Total emojis | Correct to select |
150
+ |------------|-------------|-------------------|
151
+ | easy | 4 | 2 |
152
+ | medium | 6 | 3 |
153
+ | hard | 8 | 4 |
154
+
155
+ There are 5 categories (animals, food, vehicles, nature, sports) with 20 emojis each. Distractors are drawn from all other categories. Answer format: sorted comma-separated zero-based indices, e.g. `"0,2,4"`.
156
+
117
157
  ### Mixed CAPTCHA
118
158
 
119
159
  ```typescript
@@ -129,9 +169,9 @@ const captcha = new K9Guard({ type: 'multi', difficulty: 'easy' });
129
169
  const challenge = captcha.generate();
130
170
 
131
171
  if (challenge.steps) {
132
- // user must solve both steps
133
- const answers = challenge.steps.map(step => step.answer.toString());
134
- const userInput = JSON.stringify(answers);
172
+ // user must solve both steps; steps expose only question/nonce/expiry — not the answer
173
+ // answers are submitted as a JSON array of strings
174
+ const userInput = JSON.stringify(["22", "typescript"]);
135
175
  const isValid = captcha.validate(challenge, userInput);
136
176
  }
137
177
  ```
@@ -156,13 +196,14 @@ const isValid = captcha.validate(challenge, "paris");
156
196
 
157
197
  ### Constructor Options
158
198
 
199
+ Both `type` and `difficulty` are **required** and strictly validated. Passing an invalid value throws an error immediately.
200
+
159
201
  #### Standard CAPTCHA Options
160
202
 
161
203
  ```typescript
162
204
  interface K9GuardOptions {
163
- type: 'math' | 'text' | 'riddle' | 'sequence' | 'scramble' | 'logic' | 'reverse' | 'mixed' | 'multi';
205
+ type: 'math' | 'text' | 'sequence' | 'scramble' | 'reverse' | 'mixed' | 'multi' | 'image' | 'emoji';
164
206
  difficulty: 'easy' | 'medium' | 'hard';
165
- locale?: 'en' | 'tr'; // default: 'en'
166
207
  }
167
208
  ```
168
209
 
@@ -185,39 +226,35 @@ interface CustomQuestion {
185
226
 
186
227
  #### `generate(): CaptchaChallenge`
187
228
 
188
- Generates a new CAPTCHA challenge with unique nonce, expiry time, and hashed answer.
229
+ 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`.
189
230
 
190
231
  ```typescript
191
232
  const challenge = captcha.generate();
192
- console.log(challenge.question); // the question to show user
193
- console.log(challenge.nonce); // unique session identifier
194
- console.log(challenge.expiry); // timestamp when challenge expires
233
+ console.log(challenge.question); // the question to show the user
234
+ console.log(challenge.nonce); // unique session identifier (pass back on validate)
235
+ console.log(challenge.expiry); // Unix ms timestamp when challenge expires
236
+ console.log(challenge.image); // base64 SVG data URI (only for type: 'image')
237
+ console.log(challenge.emojis); // emoji array (only for type: 'emoji')
238
+ console.log(challenge.category); // category name (only for type: 'emoji')
239
+ // challenge.answer / .hashedAnswer / .salt — NOT present; never sent to client
195
240
  ```
196
241
 
197
242
  #### `validate(challenge: CaptchaChallenge, userInput: string): boolean`
198
243
 
199
- Validates user input against the challenge. Returns `true` if correct, `false` otherwise.
244
+ 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
+
246
+ > **⚠️ 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.
200
247
 
201
248
  ```typescript
202
249
  const isValid = captcha.validate(challenge, userAnswer);
203
- ```
204
250
 
205
- ## Testing
206
-
207
- Run the included test suite:
208
-
209
- ```bash
210
- bun run src/test.ts
251
+ // After validate(), the challenge is consumed.
252
+ // For a retry, generate a fresh challenge:
253
+ if (!isValid) {
254
+ const newChallenge = captcha.generate();
255
+ }
211
256
  ```
212
257
 
213
- Tests include:
214
- - All CAPTCHA types with correct/incorrect/edge case inputs
215
- - Custom question validation
216
- - Locale switching
217
- - Multi-step challenges
218
- - Input sanitization
219
- - Security validations
220
-
221
258
  ## Contributing
222
259
 
223
260
  We welcome contributions! Here's how you can help:
package/docs/tr/README.md CHANGED
@@ -4,15 +4,16 @@
4
4
 
5
5
  # K9Guard
6
6
 
7
- TypeScript/JavaScript projeleri için kriptografik güvenlik ve çok dilli destek sunan güvenli, hafif ve esnek bir CAPTCHA modülü.
7
+ TypeScript/JavaScript projeleri için kriptografik güvenlik sunan güvenli, hafif ve esnek bir CAPTCHA modülü.
8
8
 
9
9
  ## Özellikler
10
10
 
11
11
  - **Kriptografik Güvenlik**: NIST SP 800-90A standardına uyumluluk sağlanmıştır
12
- - **Çok Dilli Destek**: Bulmaca ve mantık soruları için İngilizce ve Türkçe dil desteği mevcuttur
13
- - **9 CAPTCHA Türü**: Matematik, metin, bulmaca, dizi, karıştırma, mantık, ters çevirme, karma ve çok adımlı doğrulama yöntemleri
14
- - **Güvenlik Odaklı**: SHA-256 tuzlu hash algoritması, nonce tabanlı oturum yönetimi ve 5 dakikalık geçerlilik süresi
15
- - **Girdi Doğrulama**: Enjeksiyon saldırılarını önlemek için uzunluk sınırlamaları, tip kontrolü ve sanitizasyon
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
+ - **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
+ - **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
+ - **Katı Yapılandırma**: Geçersiz `type` veya `difficulty` değerleri anında hata fırlatır; sessiz fallback yoktur
16
+ - **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
16
17
  - **Özel Sorular**: Doğrulama ve sanitizasyon ile kendi sorularınızı tanımlama desteği
17
18
  - **Sıfır Bağımlılık**: Harici bağımlılık gerektirmeyen hafif yapı
18
19
  - **Kapsamlı Test Edilmiş**: Uç durumlar ve güvenlik senaryoları dahil olmak üzere geniş test kapsama alanı
@@ -32,8 +33,7 @@ import K9Guard from "k9guard";
32
33
 
33
34
  const captcha = new K9Guard({
34
35
  type: 'math',
35
- difficulty: 'medium',
36
- locale: 'tr'
36
+ difficulty: 'medium'
37
37
  });
38
38
 
39
39
  // doğrulama sorusu oluştur
@@ -69,15 +69,6 @@ const challenge = captcha.generate();
69
69
  // Cevap: "aB2xY9"
70
70
  ```
71
71
 
72
- ### Bulmaca CAPTCHA
73
-
74
- ```typescript
75
- const captcha = new K9Guard({ type: 'riddle', difficulty: 'easy', locale: 'tr' });
76
- const challenge = captcha.generate();
77
- // Çıktı: "Tuşları vardır ama kilit açamaz. Nedir?"
78
- // Cevap: "piyano"
79
- ```
80
-
81
72
  ### Dizi CAPTCHA
82
73
 
83
74
  ```typescript
@@ -92,28 +83,77 @@ const challenge = captcha.generate();
92
83
  ```typescript
93
84
  const captcha = new K9Guard({ type: 'scramble', difficulty: 'easy' });
94
85
  const challenge = captcha.generate();
95
- // Çıktı: "iked"
96
- // Cevap: "kedi"
86
+ // Çıktı: "tac"
87
+ // Cevap: "cat"
97
88
  ```
98
89
 
99
- ### Mantık CAPTCHA
90
+ ### Ters Çevirme CAPTCHA
100
91
 
101
92
  ```typescript
102
- const captcha = new K9Guard({ type: 'logic', difficulty: 'easy', locale: 'tr' });
93
+ const captcha = new K9Guard({ type: 'reverse', difficulty: 'easy' });
103
94
  const challenge = captcha.generate();
104
- // Çıktı: "Su kuru bir maddedir. Doğru mu Yanlış mı?"
105
- // Cevap: "yanlış"
95
+ // Çıktı: "god"
96
+ // Cevap: "dog"
106
97
  ```
107
98
 
108
- ### Ters Çevirme CAPTCHA
99
+ ### Görsel CAPTCHA
109
100
 
110
101
  ```typescript
111
- const captcha = new K9Guard({ type: 'reverse', difficulty: 'easy' });
102
+ const captcha = new K9Guard({ type: 'image', difficulty: 'medium' });
103
+ const challenge = captcha.generate();
104
+
105
+ // challenge.image — doğrudan <img> etiketinde kullanılabilecek base64 SVG data URI
106
+ // challenge.question — "Type the characters shown in the image"
107
+ console.log(challenge.image); // "data:image/svg+xml;base64,..."
108
+
109
+ // kullanıcı yanıtını doğrula (büyük/küçük harf duyarsız)
110
+ const isValid = captcha.validate(challenge, "aB3z");
111
+ if (isValid) {
112
+ console.log("Erişim izni verildi!");
113
+ } else {
114
+ console.log("Yanlış cevap!");
115
+ }
116
+ ```
117
+
118
+ Görsel CAPTCHA'nın güvenlik özellikleri:
119
+ - **Karakter başına rotasyon ve offset** — rastgele renk ve boyutla OCR direnci
120
+ - **Sinüzoidal dalga katmanları** — zorluk seviyesine orantılı üst üste bindirilir
121
+ - **Gürültü çizgileri ve noktaları** — basit segmentasyon saldırılarını engeller
122
+ - **Büyük/küçük harf duyarsız doğrulama** — kullanıcı hem büyük hem küçük harf girebilir
123
+ - **Sıfır dış bağımlılık** — tamamen sunucu tarafında saf SVG ile üretilir
124
+
125
+ ### Emoji CAPTCHA
126
+
127
+ ```typescript
128
+ const captcha = new K9Guard({ type: 'emoji', difficulty: 'medium' });
112
129
  const challenge = captcha.generate();
113
- // Çıktı: "köpek"
114
- // Cevap: "kepök"
130
+
131
+ // challenge.emojis — gösterilecek emoji dizisi (medium için 6 adet)
132
+ // challenge.category — hedef kategori adı (örn. "animals")
133
+ // challenge.question — "Select all animals from the list (6 emojis, 3 correct)"
134
+ console.log(challenge.emojis); // ["🐶", "🍎", "🚗", "🐱", "🌸", "🏀"]
135
+ console.log(challenge.category); // "animals"
136
+
137
+ // kullanıcı, doğru emojilerin sıfır tabanlı indekslerini virgülle ayırarak gönderir
138
+ // örn. emojis[0] ve emojis[3] hayvan ise: "0,3"
139
+ const isValid = captcha.validate(challenge, "0,3");
140
+ if (isValid) {
141
+ console.log("Erişim izni verildi!");
142
+ } else {
143
+ console.log("Yanlış cevap!");
144
+ }
115
145
  ```
116
146
 
147
+ Zorluk seviyesi gösterilen emoji sayısını ve doğru seçilmesi gereken emoji sayısını belirler:
148
+
149
+ | Zorluk | Toplam emoji | Seçilmesi gereken |
150
+ |---------|-------------|-------------------|
151
+ | easy | 4 | 2 |
152
+ | medium | 6 | 3 |
153
+ | hard | 8 | 4 |
154
+
155
+ 5 kategori mevcuttur (animals, food, vehicles, nature, sports), her birinde 20 emoji bulunur. Yanıltıcı emojiler diğer kategorilerden seçilir. Cevap formatı: sıralanmış, virgülle ayrılmış sıfır tabanlı indeksler; örn. `"0,2,4"`.
156
+
117
157
  ### Karma CAPTCHA
118
158
 
119
159
  ```typescript
@@ -129,9 +169,9 @@ const captcha = new K9Guard({ type: 'multi', difficulty: 'easy' });
129
169
  const challenge = captcha.generate();
130
170
 
131
171
  if (challenge.steps) {
132
- // kullanıcı her iki adımı da çözmelidir
133
- const answers = challenge.steps.map(step => step.answer.toString());
134
- const userInput = JSON.stringify(answers);
172
+ // kullanıcı her iki adımı da çözmelidir; steps yalnızca question/nonce/expiry içerir
173
+ // cevaplar JSON dizisi olarak gönderilir
174
+ const userInput = JSON.stringify(["22", "typescript"]);
135
175
  const isValid = captcha.validate(challenge, userInput);
136
176
  }
137
177
  ```
@@ -156,13 +196,14 @@ const isValid = captcha.validate(challenge, "ankara");
156
196
 
157
197
  ### Yapıcı Metod Seçenekleri
158
198
 
199
+ `type` ve `difficulty` alanları **zorunludur** ve katı şekilde doğrulanır. Geçersiz bir değer iletildiğinde constructor anında hata fırlatır.
200
+
159
201
  #### Standart CAPTCHA Seçenekleri
160
202
 
161
203
  ```typescript
162
204
  interface K9GuardOptions {
163
- type: 'math' | 'text' | 'riddle' | 'sequence' | 'scramble' | 'logic' | 'reverse' | 'mixed' | 'multi';
205
+ type: 'math' | 'text' | 'sequence' | 'scramble' | 'reverse' | 'mixed' | 'multi' | 'image' | 'emoji';
164
206
  difficulty: 'easy' | 'medium' | 'hard';
165
- locale?: 'en' | 'tr'; // varsayılan: 'en'
166
207
  }
167
208
  ```
168
209
 
@@ -185,39 +226,35 @@ interface CustomQuestion {
185
226
 
186
227
  #### `generate(): CaptchaChallenge`
187
228
 
188
- Benzersiz nonce, geçerlilik süresi ve hash'lenmiş cevap içeren yeni bir CAPTCHA doğrulaması oluşturur.
229
+ İ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.
189
230
 
190
231
  ```typescript
191
232
  const challenge = captcha.generate();
192
- console.log(challenge.question); // kullanıcıya gösterilecek soru
193
- console.log(challenge.nonce); // benzersiz oturum tanımlayıcısı
194
- console.log(challenge.expiry); // doğrulamanın geçerlilik süresi sona erme zamanı
233
+ console.log(challenge.question); // kullanıcıya gösterilecek soru
234
+ console.log(challenge.nonce); // benzersiz oturum tanımlayıcısı (validate'e geri gönderilir)
235
+ console.log(challenge.expiry); // Unix ms cinsinden geçerlilik bitiş zamanı
236
+ console.log(challenge.image); // base64 SVG data URI (yalnızca type: 'image' için)
237
+ console.log(challenge.emojis); // emoji dizisi (yalnızca type: 'emoji' için)
238
+ console.log(challenge.category); // kategori adı (yalnızca type: 'emoji' için)
239
+ // challenge.answer / .hashedAnswer / .salt — MEVCUT DEĞİL; istemciye hiç gönderilmez
195
240
  ```
196
241
 
197
242
  #### `validate(challenge: CaptchaChallenge, userInput: string): boolean`
198
243
 
199
- Kullanıcı girdisini doğrulama ile karşılaştırır. Doğruysa `true`, yanlışsa `false` değeri döndürür.
244
+ 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
+
246
+ > **⚠️ 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.
200
247
 
201
248
  ```typescript
202
249
  const isValid = captcha.validate(challenge, userAnswer);
203
- ```
204
250
 
205
- ## Test Etme
206
-
207
- Dahil edilen test paketini çalıştırın:
208
-
209
- ```bash
210
- bun run src/test.ts
251
+ // validate() çağrısından sonra challenge tüketilir.
252
+ // Yeniden deneme için yeni bir challenge üretilmeli:
253
+ if (!isValid) {
254
+ const newChallenge = captcha.generate();
255
+ }
211
256
  ```
212
257
 
213
- Testler şunları içerir:
214
- - Tüm CAPTCHA türleri için doğru/yanlış/uç durum girdileri
215
- - Özel soru doğrulama senaryoları
216
- - Dil değiştirme işlemleri
217
- - Çok adımlı doğrulamalar
218
- - Girdi sanitizasyonu
219
- - Güvenlik doğrulamaları
220
-
221
258
  ## Katkıda Bulunma
222
259
 
223
260
  Katkılarınızı memnuniyetle karşılıyoruz! Nasıl yardımcı olabilirsiniz:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "k9guard",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "A secure, lightweight, and flexible CAPTCHA module for TypeScript/JavaScript projects with cryptographic security and multi-language support",
5
5
  "main": "index.ts",
6
6
  "type": "module",
@@ -47,12 +47,12 @@
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/bun": "latest",
50
- "@types/node": "^20.0.0",
51
- "@typescript-eslint/eslint-plugin": "^6.0.0",
52
- "@typescript-eslint/parser": "^6.0.0",
53
- "eslint": "^8.0.0",
54
- "tsx": "^4.0.0",
55
- "typescript": "^5.0.0"
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
56
  },
57
57
  "peerDependencies": {
58
58
  "typescript": "^5"
package/src/K9Guard.ts CHANGED
@@ -7,7 +7,7 @@ export class K9Guard {
7
7
  private options: K9GuardOptions | K9GuardCustomOptions;
8
8
  private generator: CaptchaGenerator;
9
9
 
10
- constructor(options: K9GuardOptions | K9GuardCustomOptions | { type: 'custom'; questions: CustomQuestion[] } = { type: 'math', difficulty: 'medium' }) {
10
+ constructor(options: K9GuardOptions | K9GuardCustomOptions | { type: 'custom'; questions: CustomQuestion[] }) {
11
11
  const processedOptions = this.processOptions(options);
12
12
  this.generator = new CaptchaGenerator(processedOptions);
13
13
  this.options = processedOptions;
@@ -15,7 +15,7 @@ export class K9Guard {
15
15
 
16
16
  private processOptions(options: unknown): K9GuardOptions | K9GuardCustomOptions {
17
17
  if (typeof options !== 'object' || options === null) {
18
- return { type: 'math', difficulty: 'medium', locale: 'en' };
18
+ throw new Error('Options must be an object');
19
19
  }
20
20
 
21
21
  const opt = options as Record<string, unknown>;
@@ -36,10 +36,19 @@ export class K9Guard {
36
36
  } as K9GuardCustomOptions;
37
37
  }
38
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
+
39
49
  return {
40
- type: opt.type || 'math',
41
- difficulty: opt.difficulty || 'medium',
42
- locale: opt.locale || 'en'
50
+ type: opt.type,
51
+ difficulty: opt.difficulty
43
52
  } as K9GuardOptions;
44
53
  }
45
54
 
@@ -56,12 +65,19 @@ export class K9Guard {
56
65
  return false;
57
66
  }
58
67
 
59
- const now = Date.now();
60
- if (now > challenge.expiry) {
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) {
61
77
  return false;
62
78
  }
63
79
 
64
- return CaptchaValidator.validate(challenge, userInput);
80
+ return CaptchaValidator.validate(stored, userInput);
65
81
  }
66
82
 
67
83
  private isValidChallenge(challenge: unknown): boolean {
@@ -74,10 +90,8 @@ export class K9Guard {
74
90
  return (
75
91
  typeof c.type === 'string' &&
76
92
  typeof c.question === 'string' &&
77
- typeof c.nonce === 'string' &&
78
- typeof c.expiry === 'number' &&
79
- typeof c.hashedAnswer === 'string' &&
80
- typeof c.salt === 'string'
93
+ typeof c.nonce === 'string' && c.nonce.length > 0 &&
94
+ typeof c.expiry === 'number'
81
95
  );
82
96
  }
83
97
  }