k9guard 1.0.1 → 1.0.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/LICENSE +21 -0
- package/README.md +74 -34
- package/docs/tr/README.md +76 -36
- package/package.json +7 -7
- package/src/K9Guard.ts +18 -10
- package/src/core/captchaGenerator.ts +131 -66
- package/src/core/captchaValidator.ts +76 -11
- package/src/types/index.ts +34 -22
- package/src/utils/crypto.ts +83 -250
- package/src/utils/emojiGenerator.ts +88 -0
- package/src/utils/imageGenerator.ts +152 -0
- package/src/utils/reverseGenerator.ts +5 -1
- package/src/utils/scrambleGenerator.ts +25 -13
- package/src/utils/sequenceGenerator.ts +5 -8
- package/src/locale/en.json +0 -46
- package/src/locale/tr.json +0 -46
- package/src/utils/logicGenerator.ts +0 -18
- package/src/utils/riddleBank.ts +0 -18
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,14 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
# K9Guard
|
|
6
6
|
|
|
7
|
-
A secure, lightweight, and flexible CAPTCHA module for TypeScript/JavaScript projects with cryptographic security
|
|
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
|
-
- **
|
|
13
|
-
- **
|
|
14
|
-
- **Security First**: SHA-256 salted hashing, nonce-based session management, and 5-minute expiry
|
|
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
|
|
15
14
|
- **Input Validation**: Length limits, type checking, and sanitization to prevent injection attacks
|
|
16
15
|
- **Custom Questions**: Support for your own questions with validation and sanitization
|
|
17
16
|
- **Zero Dependencies**: Lightweight with no external dependencies
|
|
@@ -32,8 +31,7 @@ import K9Guard from "k9guard";
|
|
|
32
31
|
|
|
33
32
|
const captcha = new K9Guard({
|
|
34
33
|
type: 'math',
|
|
35
|
-
difficulty: 'medium'
|
|
36
|
-
locale: 'en'
|
|
34
|
+
difficulty: 'medium'
|
|
37
35
|
});
|
|
38
36
|
|
|
39
37
|
// generate a challenge
|
|
@@ -69,15 +67,6 @@ const challenge = captcha.generate();
|
|
|
69
67
|
// Answer: "aB2xY9"
|
|
70
68
|
```
|
|
71
69
|
|
|
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
70
|
### Sequence CAPTCHA
|
|
82
71
|
|
|
83
72
|
```typescript
|
|
@@ -96,24 +85,73 @@ const challenge = captcha.generate();
|
|
|
96
85
|
// Answer: "cat"
|
|
97
86
|
```
|
|
98
87
|
|
|
99
|
-
###
|
|
88
|
+
### Reverse CAPTCHA
|
|
100
89
|
|
|
101
90
|
```typescript
|
|
102
|
-
const captcha = new K9Guard({ type: '
|
|
91
|
+
const captcha = new K9Guard({ type: 'reverse', difficulty: 'easy' });
|
|
103
92
|
const challenge = captcha.generate();
|
|
104
|
-
// Output: "
|
|
105
|
-
// Answer: "
|
|
93
|
+
// Output: "god"
|
|
94
|
+
// Answer: "dog"
|
|
106
95
|
```
|
|
107
96
|
|
|
108
|
-
###
|
|
97
|
+
### Image CAPTCHA
|
|
109
98
|
|
|
110
99
|
```typescript
|
|
111
|
-
const captcha = new K9Guard({ type: '
|
|
100
|
+
const captcha = new K9Guard({ type: 'image', difficulty: 'medium' });
|
|
112
101
|
const challenge = captcha.generate();
|
|
113
|
-
|
|
114
|
-
//
|
|
102
|
+
|
|
103
|
+
// challenge.image — base64 SVG data URI, render it directly in an <img> tag
|
|
104
|
+
// challenge.question — "Type the characters shown in the image"
|
|
105
|
+
console.log(challenge.image); // "data:image/svg+xml;base64,..."
|
|
106
|
+
|
|
107
|
+
// validate user input (case-insensitive)
|
|
108
|
+
const isValid = captcha.validate(challenge, "aB3z");
|
|
109
|
+
if (isValid) {
|
|
110
|
+
console.log("Access granted!");
|
|
111
|
+
} else {
|
|
112
|
+
console.log("Wrong answer!");
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The image is a distorted SVG with:
|
|
117
|
+
- **Rotated & offset characters** per-glyph, randomized color and size
|
|
118
|
+
- **Sinusoidal wave overlays** proportional to difficulty
|
|
119
|
+
- **Noise lines and dots** that break simple segmentation attacks
|
|
120
|
+
- **Case-insensitive validation** — user may type upper or lowercase
|
|
121
|
+
- **No external dependencies** — pure SVG generated server-side
|
|
122
|
+
|
|
123
|
+
### Emoji CAPTCHA
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
const captcha = new K9Guard({ type: 'emoji', difficulty: 'medium' });
|
|
127
|
+
const challenge = captcha.generate();
|
|
128
|
+
|
|
129
|
+
// challenge.emojis — array of emojis to display (6 for medium)
|
|
130
|
+
// challenge.category — the target category name (e.g. "animals")
|
|
131
|
+
// challenge.question — "Select all animals from the list (6 emojis, 3 correct)"
|
|
132
|
+
console.log(challenge.emojis); // ["🐶", "🍎", "🚗", "🐱", "🌸", "🏀"]
|
|
133
|
+
console.log(challenge.category); // "animals"
|
|
134
|
+
|
|
135
|
+
// user submits sorted comma-separated zero-based indices of the correct emojis
|
|
136
|
+
// e.g. if emojis[0] and emojis[3] are animals: "0,3"
|
|
137
|
+
const isValid = captcha.validate(challenge, "0,3");
|
|
138
|
+
if (isValid) {
|
|
139
|
+
console.log("Access granted!");
|
|
140
|
+
} else {
|
|
141
|
+
console.log("Wrong answer!");
|
|
142
|
+
}
|
|
115
143
|
```
|
|
116
144
|
|
|
145
|
+
Difficulty controls the number of emojis shown and correct answers required:
|
|
146
|
+
|
|
147
|
+
| Difficulty | Total emojis | Correct to select |
|
|
148
|
+
|------------|-------------|-------------------|
|
|
149
|
+
| easy | 4 | 2 |
|
|
150
|
+
| medium | 6 | 3 |
|
|
151
|
+
| hard | 8 | 4 |
|
|
152
|
+
|
|
153
|
+
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"`.
|
|
154
|
+
|
|
117
155
|
### Mixed CAPTCHA
|
|
118
156
|
|
|
119
157
|
```typescript
|
|
@@ -129,9 +167,9 @@ const captcha = new K9Guard({ type: 'multi', difficulty: 'easy' });
|
|
|
129
167
|
const challenge = captcha.generate();
|
|
130
168
|
|
|
131
169
|
if (challenge.steps) {
|
|
132
|
-
// user must solve both steps
|
|
133
|
-
|
|
134
|
-
const userInput = JSON.stringify(
|
|
170
|
+
// user must solve both steps; steps expose only question/nonce/expiry — not the answer
|
|
171
|
+
// answers are submitted as a JSON array of strings
|
|
172
|
+
const userInput = JSON.stringify(["22", "typescript"]);
|
|
135
173
|
const isValid = captcha.validate(challenge, userInput);
|
|
136
174
|
}
|
|
137
175
|
```
|
|
@@ -160,9 +198,8 @@ const isValid = captcha.validate(challenge, "paris");
|
|
|
160
198
|
|
|
161
199
|
```typescript
|
|
162
200
|
interface K9GuardOptions {
|
|
163
|
-
type: 'math' | 'text' | '
|
|
201
|
+
type: 'math' | 'text' | 'sequence' | 'scramble' | 'reverse' | 'mixed' | 'multi' | 'image' | 'emoji';
|
|
164
202
|
difficulty: 'easy' | 'medium' | 'hard';
|
|
165
|
-
locale?: 'en' | 'tr'; // default: 'en'
|
|
166
203
|
}
|
|
167
204
|
```
|
|
168
205
|
|
|
@@ -185,18 +222,22 @@ interface CustomQuestion {
|
|
|
185
222
|
|
|
186
223
|
#### `generate(): CaptchaChallenge`
|
|
187
224
|
|
|
188
|
-
Generates a new CAPTCHA challenge
|
|
225
|
+
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
226
|
|
|
190
227
|
```typescript
|
|
191
228
|
const challenge = captcha.generate();
|
|
192
|
-
console.log(challenge.question);
|
|
193
|
-
console.log(challenge.nonce);
|
|
194
|
-
console.log(challenge.expiry);
|
|
229
|
+
console.log(challenge.question); // the question to show the user
|
|
230
|
+
console.log(challenge.nonce); // unique session identifier (pass back on validate)
|
|
231
|
+
console.log(challenge.expiry); // Unix ms timestamp when challenge expires
|
|
232
|
+
console.log(challenge.image); // base64 SVG data URI (only for type: 'image')
|
|
233
|
+
console.log(challenge.emojis); // emoji array (only for type: 'emoji')
|
|
234
|
+
console.log(challenge.category); // category name (only for type: 'emoji')
|
|
235
|
+
// challenge.answer / .hashedAnswer / .salt — NOT present; never sent to client
|
|
195
236
|
```
|
|
196
237
|
|
|
197
238
|
#### `validate(challenge: CaptchaChallenge, userInput: string): boolean`
|
|
198
239
|
|
|
199
|
-
Validates user input against the challenge. Returns `true` if correct, `false` otherwise.
|
|
240
|
+
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.
|
|
200
241
|
|
|
201
242
|
```typescript
|
|
202
243
|
const isValid = captcha.validate(challenge, userAnswer);
|
|
@@ -213,7 +254,6 @@ bun run src/test.ts
|
|
|
213
254
|
Tests include:
|
|
214
255
|
- All CAPTCHA types with correct/incorrect/edge case inputs
|
|
215
256
|
- Custom question validation
|
|
216
|
-
- Locale switching
|
|
217
257
|
- Multi-step challenges
|
|
218
258
|
- Input sanitization
|
|
219
259
|
- Security validations
|
package/docs/tr/README.md
CHANGED
|
@@ -4,14 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
# K9Guard
|
|
6
6
|
|
|
7
|
-
TypeScript/JavaScript projeleri için kriptografik güvenlik
|
|
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
|
-
-
|
|
13
|
-
- **
|
|
14
|
-
- **Güvenlik Odaklı**: SHA-256 tuzlu hash algoritması, nonce tabanlı oturum yönetimi ve 5 dakikalık geçerlilik süresi
|
|
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
|
|
15
14
|
- **Girdi Doğrulama**: Enjeksiyon saldırılarını önlemek için uzunluk sınırlamaları, tip kontrolü ve sanitizasyon
|
|
16
15
|
- **Özel Sorular**: Doğrulama ve sanitizasyon ile kendi sorularınızı tanımlama desteği
|
|
17
16
|
- **Sıfır Bağımlılık**: Harici bağımlılık gerektirmeyen hafif yapı
|
|
@@ -32,8 +31,7 @@ import K9Guard from "k9guard";
|
|
|
32
31
|
|
|
33
32
|
const captcha = new K9Guard({
|
|
34
33
|
type: 'math',
|
|
35
|
-
difficulty: 'medium'
|
|
36
|
-
locale: 'tr'
|
|
34
|
+
difficulty: 'medium'
|
|
37
35
|
});
|
|
38
36
|
|
|
39
37
|
// doğrulama sorusu oluştur
|
|
@@ -69,15 +67,6 @@ const challenge = captcha.generate();
|
|
|
69
67
|
// Cevap: "aB2xY9"
|
|
70
68
|
```
|
|
71
69
|
|
|
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
70
|
### Dizi CAPTCHA
|
|
82
71
|
|
|
83
72
|
```typescript
|
|
@@ -92,28 +81,77 @@ const challenge = captcha.generate();
|
|
|
92
81
|
```typescript
|
|
93
82
|
const captcha = new K9Guard({ type: 'scramble', difficulty: 'easy' });
|
|
94
83
|
const challenge = captcha.generate();
|
|
95
|
-
// Çıktı: "
|
|
96
|
-
// Cevap: "
|
|
84
|
+
// Çıktı: "tac"
|
|
85
|
+
// Cevap: "cat"
|
|
97
86
|
```
|
|
98
87
|
|
|
99
|
-
###
|
|
88
|
+
### Ters Çevirme CAPTCHA
|
|
100
89
|
|
|
101
90
|
```typescript
|
|
102
|
-
const captcha = new K9Guard({ type: '
|
|
91
|
+
const captcha = new K9Guard({ type: 'reverse', difficulty: 'easy' });
|
|
103
92
|
const challenge = captcha.generate();
|
|
104
|
-
// Çıktı: "
|
|
105
|
-
// Cevap: "
|
|
93
|
+
// Çıktı: "god"
|
|
94
|
+
// Cevap: "dog"
|
|
106
95
|
```
|
|
107
96
|
|
|
108
|
-
###
|
|
97
|
+
### Görsel CAPTCHA
|
|
109
98
|
|
|
110
99
|
```typescript
|
|
111
|
-
const captcha = new K9Guard({ type: '
|
|
100
|
+
const captcha = new K9Guard({ type: 'image', difficulty: 'medium' });
|
|
101
|
+
const challenge = captcha.generate();
|
|
102
|
+
|
|
103
|
+
// challenge.image — doğrudan <img> etiketinde kullanılabilecek base64 SVG data URI
|
|
104
|
+
// challenge.question — "Type the characters shown in the image"
|
|
105
|
+
console.log(challenge.image); // "data:image/svg+xml;base64,..."
|
|
106
|
+
|
|
107
|
+
// kullanıcı yanıtını doğrula (büyük/küçük harf duyarsız)
|
|
108
|
+
const isValid = captcha.validate(challenge, "aB3z");
|
|
109
|
+
if (isValid) {
|
|
110
|
+
console.log("Erişim izni verildi!");
|
|
111
|
+
} else {
|
|
112
|
+
console.log("Yanlış cevap!");
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Görsel CAPTCHA'nın güvenlik özellikleri:
|
|
117
|
+
- **Karakter başına rotasyon ve offset** — rastgele renk ve boyutla OCR direnci
|
|
118
|
+
- **Sinüzoidal dalga katmanları** — zorluk seviyesine orantılı üst üste bindirilir
|
|
119
|
+
- **Gürültü çizgileri ve noktaları** — basit segmentasyon saldırılarını engeller
|
|
120
|
+
- **Büyük/küçük harf duyarsız doğrulama** — kullanıcı hem büyük hem küçük harf girebilir
|
|
121
|
+
- **Sıfır dış bağımlılık** — tamamen sunucu tarafında saf SVG ile üretilir
|
|
122
|
+
|
|
123
|
+
### Emoji CAPTCHA
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
const captcha = new K9Guard({ type: 'emoji', difficulty: 'medium' });
|
|
112
127
|
const challenge = captcha.generate();
|
|
113
|
-
|
|
114
|
-
//
|
|
128
|
+
|
|
129
|
+
// challenge.emojis — gösterilecek emoji dizisi (medium için 6 adet)
|
|
130
|
+
// challenge.category — hedef kategori adı (örn. "animals")
|
|
131
|
+
// challenge.question — "Select all animals from the list (6 emojis, 3 correct)"
|
|
132
|
+
console.log(challenge.emojis); // ["🐶", "🍎", "🚗", "🐱", "🌸", "🏀"]
|
|
133
|
+
console.log(challenge.category); // "animals"
|
|
134
|
+
|
|
135
|
+
// kullanıcı, doğru emojilerin sıfır tabanlı indekslerini virgülle ayırarak gönderir
|
|
136
|
+
// örn. emojis[0] ve emojis[3] hayvan ise: "0,3"
|
|
137
|
+
const isValid = captcha.validate(challenge, "0,3");
|
|
138
|
+
if (isValid) {
|
|
139
|
+
console.log("Erişim izni verildi!");
|
|
140
|
+
} else {
|
|
141
|
+
console.log("Yanlış cevap!");
|
|
142
|
+
}
|
|
115
143
|
```
|
|
116
144
|
|
|
145
|
+
Zorluk seviyesi gösterilen emoji sayısını ve doğru seçilmesi gereken emoji sayısını belirler:
|
|
146
|
+
|
|
147
|
+
| Zorluk | Toplam emoji | Seçilmesi gereken |
|
|
148
|
+
|---------|-------------|-------------------|
|
|
149
|
+
| easy | 4 | 2 |
|
|
150
|
+
| medium | 6 | 3 |
|
|
151
|
+
| hard | 8 | 4 |
|
|
152
|
+
|
|
153
|
+
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"`.
|
|
154
|
+
|
|
117
155
|
### Karma CAPTCHA
|
|
118
156
|
|
|
119
157
|
```typescript
|
|
@@ -129,9 +167,9 @@ const captcha = new K9Guard({ type: 'multi', difficulty: 'easy' });
|
|
|
129
167
|
const challenge = captcha.generate();
|
|
130
168
|
|
|
131
169
|
if (challenge.steps) {
|
|
132
|
-
// kullanıcı her iki adımı da çözmelidir
|
|
133
|
-
|
|
134
|
-
const userInput = JSON.stringify(
|
|
170
|
+
// kullanıcı her iki adımı da çözmelidir; steps yalnızca question/nonce/expiry içerir
|
|
171
|
+
// cevaplar JSON dizisi olarak gönderilir
|
|
172
|
+
const userInput = JSON.stringify(["22", "typescript"]);
|
|
135
173
|
const isValid = captcha.validate(challenge, userInput);
|
|
136
174
|
}
|
|
137
175
|
```
|
|
@@ -160,9 +198,8 @@ const isValid = captcha.validate(challenge, "ankara");
|
|
|
160
198
|
|
|
161
199
|
```typescript
|
|
162
200
|
interface K9GuardOptions {
|
|
163
|
-
type: 'math' | 'text' | '
|
|
201
|
+
type: 'math' | 'text' | 'sequence' | 'scramble' | 'reverse' | 'mixed' | 'multi' | 'image' | 'emoji';
|
|
164
202
|
difficulty: 'easy' | 'medium' | 'hard';
|
|
165
|
-
locale?: 'en' | 'tr'; // varsayılan: 'en'
|
|
166
203
|
}
|
|
167
204
|
```
|
|
168
205
|
|
|
@@ -185,18 +222,22 @@ interface CustomQuestion {
|
|
|
185
222
|
|
|
186
223
|
#### `generate(): CaptchaChallenge`
|
|
187
224
|
|
|
188
|
-
|
|
225
|
+
İ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
226
|
|
|
190
227
|
```typescript
|
|
191
228
|
const challenge = captcha.generate();
|
|
192
|
-
console.log(challenge.question);
|
|
193
|
-
console.log(challenge.nonce);
|
|
194
|
-
console.log(challenge.expiry);
|
|
229
|
+
console.log(challenge.question); // kullanıcıya gösterilecek soru
|
|
230
|
+
console.log(challenge.nonce); // benzersiz oturum tanımlayıcısı (validate'e geri gönderilir)
|
|
231
|
+
console.log(challenge.expiry); // Unix ms cinsinden geçerlilik bitiş zamanı
|
|
232
|
+
console.log(challenge.image); // base64 SVG data URI (yalnızca type: 'image' için)
|
|
233
|
+
console.log(challenge.emojis); // emoji dizisi (yalnızca type: 'emoji' için)
|
|
234
|
+
console.log(challenge.category); // kategori adı (yalnızca type: 'emoji' için)
|
|
235
|
+
// challenge.answer / .hashedAnswer / .salt — MEVCUT DEĞİL; istemciye hiç gönderilmez
|
|
195
236
|
```
|
|
196
237
|
|
|
197
238
|
#### `validate(challenge: CaptchaChallenge, userInput: string): boolean`
|
|
198
239
|
|
|
199
|
-
Kullanıcı girdisini
|
|
240
|
+
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.
|
|
200
241
|
|
|
201
242
|
```typescript
|
|
202
243
|
const isValid = captcha.validate(challenge, userAnswer);
|
|
@@ -213,7 +254,6 @@ bun run src/test.ts
|
|
|
213
254
|
Testler şunları içerir:
|
|
214
255
|
- Tüm CAPTCHA türleri için doğru/yanlış/uç durum girdileri
|
|
215
256
|
- Özel soru doğrulama senaryoları
|
|
216
|
-
- Dil değiştirme işlemleri
|
|
217
257
|
- Çok adımlı doğrulamalar
|
|
218
258
|
- Girdi sanitizasyonu
|
|
219
259
|
- Güvenlik doğrulamaları
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "k9guard",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
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.
|
|
51
|
-
"@typescript-eslint/eslint-plugin": "^6.
|
|
52
|
-
"@typescript-eslint/parser": "^6.
|
|
53
|
-
"eslint": "^8.
|
|
54
|
-
"tsx": "^4.
|
|
55
|
-
"typescript": "^5.
|
|
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
|
@@ -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'
|
|
18
|
+
return { type: 'math', difficulty: 'medium' };
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
const opt = options as Record<string, unknown>;
|
|
@@ -36,10 +36,12 @@ export class K9Guard {
|
|
|
36
36
|
} as K9GuardCustomOptions;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
const validTypes = ['math', 'text', 'sequence', 'scramble', 'reverse', 'mixed', 'multi', 'image', 'emoji'];
|
|
40
|
+
const type = validTypes.includes(opt.type as string) ? opt.type : 'math';
|
|
41
|
+
|
|
39
42
|
return {
|
|
40
|
-
type
|
|
41
|
-
difficulty: opt.difficulty || 'medium'
|
|
42
|
-
locale: opt.locale || 'en'
|
|
43
|
+
type,
|
|
44
|
+
difficulty: opt.difficulty || 'medium'
|
|
43
45
|
} as K9GuardOptions;
|
|
44
46
|
}
|
|
45
47
|
|
|
@@ -56,12 +58,20 @@ export class K9Guard {
|
|
|
56
58
|
return false;
|
|
57
59
|
}
|
|
58
60
|
|
|
61
|
+
// resolve the stored record by nonce; reject if not found or expired.
|
|
62
|
+
// hashedAnswer and salt come from the server-side store, never from the client,
|
|
63
|
+
// which prevents hash-injection attacks.
|
|
64
|
+
const stored = this.generator.lookup(challenge.nonce);
|
|
65
|
+
if (!stored) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
59
69
|
const now = Date.now();
|
|
60
|
-
if (now >
|
|
70
|
+
if (now > stored.expiry) {
|
|
61
71
|
return false;
|
|
62
72
|
}
|
|
63
73
|
|
|
64
|
-
return CaptchaValidator.validate(
|
|
74
|
+
return CaptchaValidator.validate(stored, userInput);
|
|
65
75
|
}
|
|
66
76
|
|
|
67
77
|
private isValidChallenge(challenge: unknown): boolean {
|
|
@@ -74,10 +84,8 @@ export class K9Guard {
|
|
|
74
84
|
return (
|
|
75
85
|
typeof c.type === 'string' &&
|
|
76
86
|
typeof c.question === 'string' &&
|
|
77
|
-
typeof c.nonce === 'string' &&
|
|
78
|
-
typeof c.expiry === 'number'
|
|
79
|
-
typeof c.hashedAnswer === 'string' &&
|
|
80
|
-
typeof c.salt === 'string'
|
|
87
|
+
typeof c.nonce === 'string' && c.nonce.length > 0 &&
|
|
88
|
+
typeof c.expiry === 'number'
|
|
81
89
|
);
|
|
82
90
|
}
|
|
83
91
|
}
|