euredact 0.0.1 → 0.2.0

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.
@@ -0,0 +1,699 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VALIDATORS = void 0;
4
+ exports.validateIban = validateIban;
5
+ exports.validateBsn = validateBsn;
6
+ exports.validateBelgianNn = validateBelgianNn;
7
+ exports.validateLuhn = validateLuhn;
8
+ exports.validateBelgianVat = validateBelgianVat;
9
+ exports.validateVatNl = validateVatNl;
10
+ exports.validateVatDe = validateVatDe;
11
+ exports.validateVatFr = validateVatFr;
12
+ exports.validateVatLu = validateVatLu;
13
+ exports.validateGermanTaxId = validateGermanTaxId;
14
+ exports.validateFrenchNir = validateFrenchNir;
15
+ exports.validateVin = validateVin;
16
+ exports.validateBic = validateBic;
17
+ exports.validateKvk = validateKvk;
18
+ exports.validateSwedishPnr = validateSwedishPnr;
19
+ exports.validateNorwegianFnr = validateNorwegianFnr;
20
+ exports.validateFinnishHetu = validateFinnishHetu;
21
+ exports.validateIcelandicKt = validateIcelandicKt;
22
+ exports.validateDanishVat = validateDanishVat;
23
+ exports.validateFinnishBusinessId = validateFinnishBusinessId;
24
+ exports.validateImei = validateImei;
25
+ exports.validateDanishCpr = validateDanishCpr;
26
+ exports.validateNorwegianOrg = validateNorwegianOrg;
27
+ exports.validateAustrianSvnr = validateAustrianSvnr;
28
+ exports.validateSwissAhv = validateSwissAhv;
29
+ exports.validateItalianCf = validateItalianCf;
30
+ exports.validateSpanishDni = validateSpanishDni;
31
+ exports.validateSpanishNie = validateSpanishNie;
32
+ exports.validatePortugueseNif = validatePortugueseNif;
33
+ exports.validatePolishPesel = validatePolishPesel;
34
+ exports.validatePolishNip = validatePolishNip;
35
+ exports.validateCzechBirthNumber = validateCzechBirthNumber;
36
+ exports.validateRomanianCnp = validateRomanianCnp;
37
+ exports.validateHungarianTaj = validateHungarianTaj;
38
+ exports.validateBulgarianEgn = validateBulgarianEgn;
39
+ exports.validateCroatianOib = validateCroatianOib;
40
+ exports.validateSlovenianEmso = validateSlovenianEmso;
41
+ exports.validateIrishPps = validateIrishPps;
42
+ exports.validateEstonianId = validateEstonianId;
43
+ exports.validateUkNhs = validateUkNhs;
44
+ exports.validateGreekAfm = validateGreekAfm;
45
+ exports.validateGreekAmka = validateGreekAmka;
46
+ function clean(candidate, chars = /[\s.\-]/g) {
47
+ return candidate.replace(chars, "");
48
+ }
49
+ function validateIban(candidate) {
50
+ const c = candidate.replace(/[\s\-]/g, "").toUpperCase();
51
+ if (c.length < 5 || !/^[A-Z]{2}/.test(c) || !/^\d{2}$/.test(c.slice(2, 4)))
52
+ return false;
53
+ const ibanLengths = {
54
+ AL: 28, AD: 24, AT: 20, AZ: 28, BH: 22, BY: 28, BE: 16, BA: 20, BR: 29, BG: 22, CR: 22, HR: 21,
55
+ CY: 28, CZ: 24, DK: 18, DO: 28, TL: 23, EE: 20, FO: 18, FI: 18, FR: 27, GE: 22, DE: 22, GI: 23,
56
+ GR: 27, GL: 18, GT: 28, HU: 28, IS: 26, IQ: 23, IE: 22, IL: 23, IT: 27, JO: 30, KZ: 20, XK: 20,
57
+ KW: 30, LV: 21, LB: 28, LI: 21, LT: 20, LU: 20, MT: 31, MR: 27, MU: 30, MC: 27, MD: 24, ME: 22,
58
+ NL: 18, MK: 19, NO: 15, PK: 24, PS: 29, PL: 28, PT: 25, QA: 29, RO: 24, LC: 32, SM: 27, SA: 24,
59
+ RS: 22, SC: 31, SK: 24, SI: 19, ES: 24, SE: 24, CH: 21, TN: 24, TR: 26, UA: 29, AE: 23, GB: 22,
60
+ VA: 22, VG: 24,
61
+ };
62
+ const country = c.slice(0, 2);
63
+ const expected = ibanLengths[country];
64
+ if (expected !== undefined && c.length !== expected)
65
+ return false;
66
+ const rearranged = c.slice(4) + c.slice(0, 4);
67
+ let numeric = "";
68
+ for (const ch of rearranged) {
69
+ if (/\d/.test(ch))
70
+ numeric += ch;
71
+ else if (/[A-Z]/.test(ch))
72
+ numeric += (ch.charCodeAt(0) - 55).toString();
73
+ else
74
+ return false;
75
+ }
76
+ // BigInt mod 97
77
+ let remainder = 0n;
78
+ for (let i = 0; i < numeric.length; i += 7) {
79
+ const chunk = remainder.toString() + numeric.slice(i, i + 7);
80
+ remainder = BigInt(chunk) % 97n;
81
+ }
82
+ return remainder === 1n;
83
+ }
84
+ function validateBsn(candidate) {
85
+ const c = clean(candidate);
86
+ if (c.length !== 9 || !/^\d{9}$/.test(c) || c === "000000000")
87
+ return false;
88
+ const weights = [9, 8, 7, 6, 5, 4, 3, 2, -1];
89
+ let total = 0;
90
+ for (let i = 0; i < 9; i++)
91
+ total += parseInt(c[i]) * weights[i];
92
+ return total % 11 === 0 && total !== 0;
93
+ }
94
+ function validateBelgianNn(candidate) {
95
+ const c = clean(candidate);
96
+ if (c.length !== 11 || !/^\d{11}$/.test(c))
97
+ return false;
98
+ const firstNine = parseInt(c.slice(0, 9));
99
+ const check = parseInt(c.slice(9, 11));
100
+ if (97 - (firstNine % 97) === check)
101
+ return true;
102
+ const firstNine2000 = parseInt("2" + c.slice(0, 9));
103
+ return 97 - (firstNine2000 % 97) === check;
104
+ }
105
+ function validateLuhn(candidate) {
106
+ const c = candidate.replace(/[\s\-]/g, "");
107
+ if (!/^\d+$/.test(c) || c.length < 12)
108
+ return false;
109
+ let total = 0;
110
+ for (let i = c.length - 1; i >= 0; i--) {
111
+ let n = parseInt(c[i]);
112
+ if ((c.length - 1 - i) % 2 === 1) {
113
+ n *= 2;
114
+ if (n > 9)
115
+ n -= 9;
116
+ }
117
+ total += n;
118
+ }
119
+ return total % 10 === 0;
120
+ }
121
+ function validateBelgianVat(candidate) {
122
+ let c = clean(candidate).toUpperCase();
123
+ if (c.startsWith("BE"))
124
+ c = c.slice(2);
125
+ if (c.length !== 10 || !/^\d{10}$/.test(c) || c[0] !== "0")
126
+ return false;
127
+ const firstPart = parseInt(c.slice(0, 8));
128
+ const check = parseInt(c.slice(8, 10));
129
+ return (firstPart % 97) === (97 - check) || (97 - (firstPart % 97)) === check;
130
+ }
131
+ function validateVatNl(candidate) {
132
+ let c = clean(candidate).toUpperCase();
133
+ if (c.startsWith("NL"))
134
+ c = c.slice(2);
135
+ if (c.length !== 12)
136
+ return false;
137
+ return /^\d{9}B\d{2}$/.test(c);
138
+ }
139
+ function validateVatDe(candidate) {
140
+ let c = clean(candidate).toUpperCase();
141
+ if (c.startsWith("DE"))
142
+ c = c.slice(2);
143
+ return c.length === 9 && /^\d{9}$/.test(c);
144
+ }
145
+ function validateVatFr(candidate) {
146
+ let c = clean(candidate).toUpperCase();
147
+ if (c.startsWith("FR"))
148
+ c = c.slice(2);
149
+ if (c.length !== 11)
150
+ return false;
151
+ for (const ch of c.slice(0, 2)) {
152
+ if (!(/\d/.test(ch) || (/[A-Z]/.test(ch) && ch !== "O" && ch !== "I")))
153
+ return false;
154
+ }
155
+ return /^\d{9}$/.test(c.slice(2));
156
+ }
157
+ function validateVatLu(candidate) {
158
+ let c = clean(candidate).toUpperCase();
159
+ if (c.startsWith("LU"))
160
+ c = c.slice(2);
161
+ return c.length === 8 && /^\d{8}$/.test(c);
162
+ }
163
+ function validateGermanTaxId(candidate) {
164
+ const c = candidate.replace(/[\s.\-/]/g, "");
165
+ if (c.length !== 11 || !/^\d{11}$/.test(c) || c[0] === "0")
166
+ return false;
167
+ let product = 10;
168
+ for (let i = 0; i < 10; i++) {
169
+ let total = (parseInt(c[i]) + product) % 10;
170
+ if (total === 0)
171
+ total = 10;
172
+ product = (total * 2) % 11;
173
+ }
174
+ const check = (11 - product) % 10;
175
+ return check === parseInt(c[10]);
176
+ }
177
+ function validateFrenchNir(candidate) {
178
+ const c = clean(candidate);
179
+ if (c.length !== 15)
180
+ return false;
181
+ let numeric = c;
182
+ const upper = c.toUpperCase();
183
+ if (!/^\d+$/.test(c)) {
184
+ if (upper.includes("A"))
185
+ numeric = upper.replace(/A/g, "0");
186
+ else if (upper.includes("B"))
187
+ numeric = upper.replace(/B/g, "0");
188
+ else
189
+ return false;
190
+ }
191
+ if (!/^\d+$/.test(numeric))
192
+ return false;
193
+ let first13 = parseInt(numeric.slice(0, 13));
194
+ if (upper.includes("A")) {
195
+ first13 = parseInt(upper.slice(0, 7).replace(/A/g, "0") + c.slice(7, 13)) - 1000000;
196
+ }
197
+ else if (upper.includes("B")) {
198
+ first13 = parseInt(upper.slice(0, 7).replace(/B/g, "0") + c.slice(7, 13)) - 2000000;
199
+ }
200
+ const check = parseInt(numeric.slice(13, 15));
201
+ return (97 - (first13 % 97)) === check;
202
+ }
203
+ function validateVin(candidate) {
204
+ const c = candidate.replace(/[\s\-]/g, "").toUpperCase();
205
+ if (c.length !== 17)
206
+ return false;
207
+ return !/[IOQ]/.test(c);
208
+ }
209
+ function validateBic(candidate) {
210
+ const c = candidate.replace(/ /g, "").toUpperCase();
211
+ if (c.length !== 8 && c.length !== 11)
212
+ return false;
213
+ if (!/^[A-Z]{4}/.test(c))
214
+ return false;
215
+ if (!/^[A-Z]{2}$/.test(c.slice(4, 6)))
216
+ return false;
217
+ if (!/^[A-Z0-9]{2}$/.test(c.slice(6, 8)))
218
+ return false;
219
+ if (c.length === 11 && !/^[A-Z0-9]{3}$/.test(c.slice(8, 11)))
220
+ return false;
221
+ return true;
222
+ }
223
+ function validateKvk(candidate) {
224
+ const c = clean(candidate);
225
+ return c.length === 8 && /^\d{8}$/.test(c);
226
+ }
227
+ function validateSwedishPnr(candidate) {
228
+ let c = candidate.replace(/[\s\-+]/g, "");
229
+ if (c.length === 12)
230
+ c = c.slice(2);
231
+ if (c.length !== 10 || !/^\d{10}$/.test(c))
232
+ return false;
233
+ const month = parseInt(c.slice(2, 4));
234
+ const day = parseInt(c.slice(4, 6));
235
+ if (month < 1 || month > 12 || day < 1 || day > 31)
236
+ return false;
237
+ let total = 0;
238
+ for (let i = 0; i < 10; i++) {
239
+ let n = parseInt(c[i]) * (i % 2 === 0 ? 2 : 1);
240
+ total += Math.floor(n / 10) + (n % 10);
241
+ }
242
+ return total % 10 === 0;
243
+ }
244
+ function validateNorwegianFnr(candidate) {
245
+ const c = candidate.replace(/[\s\-]/g, "");
246
+ if (c.length !== 11 || !/^\d{11}$/.test(c))
247
+ return false;
248
+ const d = c.split("").map(Number);
249
+ const w1 = [3, 7, 6, 1, 8, 9, 4, 5, 2];
250
+ let s1 = 0;
251
+ for (let i = 0; i < 9; i++)
252
+ s1 += d[i] * w1[i];
253
+ let c1 = 11 - (s1 % 11);
254
+ if (c1 === 11)
255
+ c1 = 0;
256
+ if (c1 === 10)
257
+ return false;
258
+ if (d[9] !== c1)
259
+ return false;
260
+ const w2 = [5, 4, 3, 2, 7, 6, 5, 4, 3, 2];
261
+ let s2 = 0;
262
+ for (let i = 0; i < 10; i++)
263
+ s2 += d[i] * w2[i];
264
+ let c2 = 11 - (s2 % 11);
265
+ if (c2 === 11)
266
+ c2 = 0;
267
+ if (c2 === 10)
268
+ return false;
269
+ return d[10] === c2;
270
+ }
271
+ function validateFinnishHetu(candidate) {
272
+ const c = candidate.trim();
273
+ if (c.length !== 11)
274
+ return false;
275
+ const digits = c.slice(0, 6);
276
+ const sep = c[6];
277
+ const individual = c.slice(7, 10);
278
+ const checkChar = c[10];
279
+ if (!/^\d{6}$/.test(digits) || !/^\d{3}$/.test(individual))
280
+ return false;
281
+ if (!"-+ABCDEFYXWVU".includes(sep))
282
+ return false;
283
+ const lookup = "0123456789ABCDEFHJKLMNPRSTUVWXY";
284
+ const num = parseInt(digits + individual);
285
+ return checkChar === lookup[num % 31];
286
+ }
287
+ function validateIcelandicKt(candidate) {
288
+ const c = candidate.replace(/[\s\-]/g, "");
289
+ if (c.length !== 10 || !/^\d{10}$/.test(c))
290
+ return false;
291
+ const d = c.split("").map(Number);
292
+ const w = [3, 2, 7, 6, 5, 4, 3, 2];
293
+ let s = 0;
294
+ for (let i = 0; i < 8; i++)
295
+ s += d[i] * w[i];
296
+ const check = (11 - (s % 11)) % 11;
297
+ if (check === 10)
298
+ return false;
299
+ return d[8] === check;
300
+ }
301
+ function validateDanishVat(candidate) {
302
+ let c = candidate.replace(/[\s\-]/g, "").toUpperCase();
303
+ if (c.startsWith("DK"))
304
+ c = c.slice(2);
305
+ if (c.length !== 8 || !/^\d{8}$/.test(c))
306
+ return false;
307
+ const d = c.split("").map(Number);
308
+ const w = [2, 7, 6, 5, 4, 3, 2, 1];
309
+ let total = 0;
310
+ for (let i = 0; i < 8; i++)
311
+ total += d[i] * w[i];
312
+ return total % 11 === 0;
313
+ }
314
+ function validateFinnishBusinessId(candidate) {
315
+ let c = candidate.replace(/\s/g, "");
316
+ if (c.includes("-")) {
317
+ const parts = c.split("-");
318
+ if (parts.length !== 2)
319
+ return false;
320
+ c = parts[0] + parts[1];
321
+ }
322
+ if (c.length !== 8 || !/^\d{8}$/.test(c))
323
+ return false;
324
+ const d = c.split("").map(Number);
325
+ const w = [7, 9, 10, 5, 8, 4, 2];
326
+ let s = 0;
327
+ for (let i = 0; i < 7; i++)
328
+ s += d[i] * w[i];
329
+ const remainder = s % 11;
330
+ let check;
331
+ if (remainder === 0)
332
+ check = 0;
333
+ else if (remainder === 1)
334
+ return false;
335
+ else
336
+ check = 11 - remainder;
337
+ return d[7] === check;
338
+ }
339
+ function validateImei(candidate) {
340
+ const c = candidate.replace(/[\s\-/]/g, "");
341
+ if (c.length !== 15 || !/^\d{15}$/.test(c))
342
+ return false;
343
+ if (c.slice(0, 8) === "00000000")
344
+ return false;
345
+ let total = 0;
346
+ for (let i = 0; i < 15; i++) {
347
+ let n = parseInt(c[i]);
348
+ if (i % 2 === 1) {
349
+ n *= 2;
350
+ if (n > 9)
351
+ n -= 9;
352
+ }
353
+ total += n;
354
+ }
355
+ return total % 10 === 0;
356
+ }
357
+ function validateDanishCpr(candidate) {
358
+ const c = candidate.replace(/[\s\-]/g, "");
359
+ if (c.length !== 10 || !/^\d{10}$/.test(c))
360
+ return false;
361
+ const day = parseInt(c.slice(0, 2));
362
+ const month = parseInt(c.slice(2, 4));
363
+ return day >= 1 && day <= 31 && month >= 1 && month <= 12;
364
+ }
365
+ function validateNorwegianOrg(candidate) {
366
+ const c = candidate.replace(/[\s\-]/g, "");
367
+ if (c.length !== 9 || !/^\d{9}$/.test(c) || !("89".includes(c[0])))
368
+ return false;
369
+ const d = c.split("").map(Number);
370
+ const w = [3, 2, 7, 6, 5, 4, 3, 2];
371
+ let s = 0;
372
+ for (let i = 0; i < 8; i++)
373
+ s += d[i] * w[i];
374
+ const remainder = s % 11;
375
+ let check;
376
+ if (remainder === 0)
377
+ check = 0;
378
+ else if (remainder === 1)
379
+ return false;
380
+ else
381
+ check = 11 - remainder;
382
+ return d[8] === check;
383
+ }
384
+ function validateAustrianSvnr(candidate) {
385
+ const c = candidate.replace(/\s/g, "");
386
+ if (c.length !== 10 || !/^\d{10}$/.test(c))
387
+ return false;
388
+ const d = c.split("").map(Number);
389
+ const weights = [3, 7, 9, 0, 5, 8, 4, 2, 1, 6];
390
+ let total = 0;
391
+ for (let i = 0; i < 10; i++)
392
+ total += d[i] * weights[i];
393
+ return total % 11 === d[3];
394
+ }
395
+ function validateSwissAhv(candidate) {
396
+ const c = clean(candidate);
397
+ if (c.length !== 13 || !/^\d{13}$/.test(c) || !c.startsWith("756"))
398
+ return false;
399
+ let total = 0;
400
+ for (let i = 0; i < 12; i++)
401
+ total += parseInt(c[i]) * (i % 2 === 0 ? 1 : 3);
402
+ const check = (10 - (total % 10)) % 10;
403
+ return check === parseInt(c[12]);
404
+ }
405
+ function validateItalianCf(candidate) {
406
+ const c = candidate.toUpperCase().trim();
407
+ if (c.length !== 16)
408
+ return false;
409
+ if (!/^[A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]\d{3}[A-Z]$/.test(c))
410
+ return false;
411
+ const oddMap = {
412
+ "0": 1, "1": 0, "2": 5, "3": 7, "4": 9, "5": 13, "6": 15, "7": 17, "8": 19, "9": 21,
413
+ A: 1, B: 0, C: 5, D: 7, E: 9, F: 13, G: 15, H: 17, I: 19, J: 21, K: 2, L: 4, M: 18, N: 20, O: 11,
414
+ P: 3, Q: 6, R: 8, S: 12, T: 14, U: 16, V: 10, W: 22, X: 25, Y: 24, Z: 23,
415
+ };
416
+ const evenMap = {
417
+ "0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9,
418
+ A: 0, B: 1, C: 2, D: 3, E: 4, F: 5, G: 6, H: 7, I: 8, J: 9, K: 10, L: 11, M: 12, N: 13, O: 14,
419
+ P: 15, Q: 16, R: 17, S: 18, T: 19, U: 20, V: 21, W: 22, X: 23, Y: 24, Z: 25,
420
+ };
421
+ let total = 0;
422
+ for (let i = 0; i < 15; i++) {
423
+ total += i % 2 === 0 ? (oddMap[c[i]] ?? 0) : (evenMap[c[i]] ?? 0);
424
+ }
425
+ return c[15] === String.fromCharCode(65 + (total % 26));
426
+ }
427
+ function validateSpanishDni(candidate) {
428
+ const c = candidate.toUpperCase().trim();
429
+ if (!/^\d{8}[A-Z]$/.test(c))
430
+ return false;
431
+ const lookup = "TRWAGMYFPDXBNJZSQVHLCKE";
432
+ return c[8] === lookup[parseInt(c.slice(0, 8)) % 23];
433
+ }
434
+ function validateSpanishNie(candidate) {
435
+ const c = candidate.toUpperCase().trim();
436
+ if (!/^[XYZ]\d{7}[A-Z]$/.test(c))
437
+ return false;
438
+ const prefixMap = { X: "0", Y: "1", Z: "2" };
439
+ const num = parseInt(prefixMap[c[0]] + c.slice(1, 8));
440
+ const lookup = "TRWAGMYFPDXBNJZSQVHLCKE";
441
+ return c[8] === lookup[num % 23];
442
+ }
443
+ function validatePortugueseNif(candidate) {
444
+ const c = clean(candidate);
445
+ if (c.length !== 9 || !/^\d{9}$/.test(c) || "04".includes(c[0]))
446
+ return false;
447
+ const weights = [9, 8, 7, 6, 5, 4, 3, 2];
448
+ let total = 0;
449
+ for (let i = 0; i < 8; i++)
450
+ total += parseInt(c[i]) * weights[i];
451
+ const remainder = total % 11;
452
+ const check = remainder < 2 ? 0 : 11 - remainder;
453
+ return parseInt(c[8]) === check;
454
+ }
455
+ function validatePolishPesel(candidate) {
456
+ const c = candidate.replace(/[\s\-]/g, "");
457
+ if (c.length !== 11 || !/^\d{11}$/.test(c))
458
+ return false;
459
+ const d = c.split("").map(Number);
460
+ const weights = [1, 3, 7, 9, 1, 3, 7, 9, 1, 3];
461
+ let total = 0;
462
+ for (let i = 0; i < 10; i++)
463
+ total += d[i] * weights[i];
464
+ return d[10] === (10 - (total % 10)) % 10;
465
+ }
466
+ function validatePolishNip(candidate) {
467
+ const c = candidate.replace(/[\s\-]/g, "");
468
+ if (c.length !== 10 || !/^\d{10}$/.test(c))
469
+ return false;
470
+ const d = c.split("").map(Number);
471
+ const weights = [6, 5, 7, 2, 3, 4, 5, 6, 7];
472
+ let total = 0;
473
+ for (let i = 0; i < 9; i++)
474
+ total += d[i] * weights[i];
475
+ return total % 11 === d[9];
476
+ }
477
+ function validateCzechBirthNumber(candidate) {
478
+ const c = candidate.replace(/[/\s]/g, "");
479
+ if (!/^\d+$/.test(c) || (c.length !== 9 && c.length !== 10))
480
+ return false;
481
+ if (c.length === 10)
482
+ return parseInt(c) % 11 === 0;
483
+ return true;
484
+ }
485
+ function validateRomanianCnp(candidate) {
486
+ const c = candidate.replace(/[\s\-]/g, "");
487
+ if (c.length !== 13 || !/^\d{13}$/.test(c) || !"12345678".includes(c[0]))
488
+ return false;
489
+ const key = "279146358279";
490
+ let total = 0;
491
+ for (let i = 0; i < 12; i++)
492
+ total += parseInt(c[i]) * parseInt(key[i]);
493
+ const remainder = total % 11;
494
+ const check = remainder === 10 ? 1 : remainder;
495
+ return parseInt(c[12]) === check;
496
+ }
497
+ function validateHungarianTaj(candidate) {
498
+ const c = candidate.replace(/[\s\-]/g, "");
499
+ if (c.length !== 9 || !/^\d{9}$/.test(c))
500
+ return false;
501
+ const d = c.split("").map(Number);
502
+ let total = 0;
503
+ for (let i = 0; i < 8; i++)
504
+ total += d[i] * (i % 2 === 0 ? 3 : 7);
505
+ return total % 10 === d[8];
506
+ }
507
+ function validateBulgarianEgn(candidate) {
508
+ const c = candidate.replace(/[\s\-]/g, "");
509
+ if (c.length !== 10 || !/^\d{10}$/.test(c))
510
+ return false;
511
+ const d = c.split("").map(Number);
512
+ const weights = [2, 4, 8, 5, 10, 9, 7, 3, 6];
513
+ let total = 0;
514
+ for (let i = 0; i < 9; i++)
515
+ total += d[i] * weights[i];
516
+ let check = total % 11;
517
+ if (check === 10)
518
+ check = 0;
519
+ return d[9] === check;
520
+ }
521
+ function validateCroatianOib(candidate) {
522
+ const c = candidate.replace(/[\s\-]/g, "");
523
+ if (c.length !== 11 || !/^\d{11}$/.test(c))
524
+ return false;
525
+ let product = 10;
526
+ for (let i = 0; i < 10; i++) {
527
+ let total = (parseInt(c[i]) + product) % 10;
528
+ if (total === 0)
529
+ total = 10;
530
+ product = (total * 2) % 11;
531
+ }
532
+ const check = (11 - product) % 10;
533
+ return check === parseInt(c[10]);
534
+ }
535
+ function validateSlovenianEmso(candidate) {
536
+ const c = candidate.replace(/[\s\-]/g, "");
537
+ if (c.length !== 13 || !/^\d{13}$/.test(c))
538
+ return false;
539
+ const d = c.split("").map(Number);
540
+ const weights = [7, 6, 5, 4, 3, 2, 7, 6, 5, 4, 3, 2];
541
+ let total = 0;
542
+ for (let i = 0; i < 12; i++)
543
+ total += d[i] * weights[i];
544
+ const remainder = total % 11;
545
+ let check = remainder === 0 ? 0 : 11 - remainder;
546
+ if (check === 10)
547
+ return false;
548
+ return d[12] === check;
549
+ }
550
+ function validateIrishPps(candidate) {
551
+ const c = candidate.toUpperCase().trim();
552
+ if (!/^\d{7}[A-W][ABWTXZ]?$/.test(c))
553
+ return false;
554
+ const weights = [8, 7, 6, 5, 4, 3, 2];
555
+ let total = 0;
556
+ for (let i = 0; i < 7; i++)
557
+ total += parseInt(c[i]) * weights[i];
558
+ if (c.length === 9 && /[A-Z]/.test(c[8]))
559
+ total += (c.charCodeAt(8) - 64) * 9;
560
+ const expected = total % 23;
561
+ const check = expected > 0 ? String.fromCharCode(64 + expected) : "W";
562
+ return c[7] === check;
563
+ }
564
+ function validateEstonianId(candidate) {
565
+ const c = candidate.replace(/[\s\-]/g, "");
566
+ if (c.length !== 11 || !/^\d{11}$/.test(c) || !"123456".includes(c[0]))
567
+ return false;
568
+ const d = c.split("").map(Number);
569
+ const w1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 1];
570
+ let s1 = 0;
571
+ for (let i = 0; i < 10; i++)
572
+ s1 += d[i] * w1[i];
573
+ s1 = s1 % 11;
574
+ if (s1 !== 10)
575
+ return d[10] === s1;
576
+ const w2 = [3, 4, 5, 6, 7, 8, 9, 1, 2, 3];
577
+ let s2 = 0;
578
+ for (let i = 0; i < 10; i++)
579
+ s2 += d[i] * w2[i];
580
+ s2 = s2 % 11;
581
+ if (s2 === 10)
582
+ s2 = 0;
583
+ return d[10] === s2;
584
+ }
585
+ function validateUkNhs(candidate) {
586
+ const c = candidate.replace(/[\s\-]/g, "");
587
+ if (c.length !== 10 || !/^\d{10}$/.test(c))
588
+ return false;
589
+ const weights = [10, 9, 8, 7, 6, 5, 4, 3, 2];
590
+ let total = 0;
591
+ for (let i = 0; i < 9; i++)
592
+ total += parseInt(c[i]) * weights[i];
593
+ let check = 11 - (total % 11);
594
+ if (check === 11)
595
+ check = 0;
596
+ if (check === 10)
597
+ return false;
598
+ return parseInt(c[9]) === check;
599
+ }
600
+ function validateGreekAfm(candidate) {
601
+ const c = candidate.replace(/[\s\-]/g, "");
602
+ if (c.length !== 9 || !/^\d{9}$/.test(c))
603
+ return false;
604
+ const d = c.split("").map(Number);
605
+ let total = 0;
606
+ for (let i = 0; i < 8; i++)
607
+ total += d[i] * Math.pow(2, 8 - i);
608
+ return d[8] === (total % 11) % 10;
609
+ }
610
+ function validateGreekAmka(candidate) {
611
+ const c = candidate.replace(/[\s\-]/g, "");
612
+ if (c.length !== 11 || !/^\d{11}$/.test(c))
613
+ return false;
614
+ let total = 0;
615
+ for (let i = c.length - 1; i >= 0; i--) {
616
+ let n = parseInt(c[i]);
617
+ if ((c.length - 1 - i) % 2 === 1) {
618
+ n *= 2;
619
+ if (n > 9)
620
+ n -= 9;
621
+ }
622
+ total += n;
623
+ }
624
+ return total % 10 === 0;
625
+ }
626
+ function validateHighEntropy(candidate) {
627
+ const clean = candidate.trim();
628
+ if (clean.length < 8)
629
+ return false;
630
+ // Short passwords (< 24 chars): require at least 1 letter, 1 digit, 1 special char
631
+ if (clean.length < 24) {
632
+ const hasLetter = /[a-zA-Z]/.test(clean);
633
+ const hasDigit = /\d/.test(clean);
634
+ const hasSpecial = /[^a-zA-Z0-9]/.test(clean);
635
+ if (hasLetter && hasDigit && hasSpecial)
636
+ return true;
637
+ // For short tokens without special chars, require entropy check
638
+ if (!hasSpecial && clean.length < 16)
639
+ return false;
640
+ }
641
+ const freq = new Map();
642
+ for (const ch of clean)
643
+ freq.set(ch, (freq.get(ch) ?? 0) + 1);
644
+ const len = clean.length;
645
+ let entropy = 0;
646
+ for (const c of freq.values()) {
647
+ const p = c / len;
648
+ entropy -= p * Math.log2(p);
649
+ }
650
+ // Hex-only strings have max ~4.0 bits entropy, use lower threshold
651
+ const isHex = /^[a-fA-F0-9]+$/.test(clean);
652
+ const threshold = isHex ? 3.0 : clean.length < 24 ? 3.0 : 3.5;
653
+ return entropy >= threshold;
654
+ }
655
+ exports.VALIDATORS = {
656
+ iban: validateIban,
657
+ bsn: validateBsn,
658
+ belgian_nn: validateBelgianNn,
659
+ luhn: validateLuhn,
660
+ belgian_vat: validateBelgianVat,
661
+ vat_nl: validateVatNl,
662
+ vat_de: validateVatDe,
663
+ vat_fr: validateVatFr,
664
+ vat_lu: validateVatLu,
665
+ german_tax_id: validateGermanTaxId,
666
+ french_nir: validateFrenchNir,
667
+ vin: validateVin,
668
+ bic: validateBic,
669
+ kvk: validateKvk,
670
+ swedish_pnr: validateSwedishPnr,
671
+ norwegian_fnr: validateNorwegianFnr,
672
+ finnish_hetu: validateFinnishHetu,
673
+ icelandic_kt: validateIcelandicKt,
674
+ danish_vat: validateDanishVat,
675
+ finnish_business_id: validateFinnishBusinessId,
676
+ norwegian_org: validateNorwegianOrg,
677
+ danish_cpr: validateDanishCpr,
678
+ imei: validateImei,
679
+ austrian_svnr: validateAustrianSvnr,
680
+ swiss_ahv: validateSwissAhv,
681
+ italian_cf: validateItalianCf,
682
+ spanish_dni: validateSpanishDni,
683
+ spanish_nie: validateSpanishNie,
684
+ portuguese_nif: validatePortugueseNif,
685
+ polish_pesel: validatePolishPesel,
686
+ polish_nip: validatePolishNip,
687
+ czech_birth_number: validateCzechBirthNumber,
688
+ romanian_cnp: validateRomanianCnp,
689
+ hungarian_taj: validateHungarianTaj,
690
+ bulgarian_egn: validateBulgarianEgn,
691
+ croatian_oib: validateCroatianOib,
692
+ slovenian_emso: validateSlovenianEmso,
693
+ irish_pps: validateIrishPps,
694
+ estonian_id: validateEstonianId,
695
+ uk_nhs: validateUkNhs,
696
+ greek_afm: validateGreekAfm,
697
+ greek_amka: validateGreekAmka,
698
+ high_entropy: validateHighEntropy,
699
+ };