@duvdu-v1/duvdu 1.1.355 → 1.1.360

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.
Files changed (56) hide show
  1. package/build/errors/pdf-generation-error.d.ts +9 -0
  2. package/build/errors/pdf-generation-error.js +15 -0
  3. package/build/guards/isEmailVerified.guard.d.ts +2 -0
  4. package/build/guards/isEmailVerified.guard.js +15 -0
  5. package/build/guards/isPhoneNumberVerified.guard.d.ts +2 -0
  6. package/build/guards/isPhoneNumberVerified.guard.js +15 -0
  7. package/build/index.d.ts +7 -1
  8. package/build/index.js +7 -1
  9. package/build/mailer/mailer.interface.d.ts +68 -0
  10. package/build/mailer/mailer.interface.js +12 -0
  11. package/build/mailer/mailer.service.d.ts +10 -0
  12. package/build/mailer/mailer.service.js +149 -0
  13. package/build/mailer/strategies/resend.strategy.d.ts +6 -0
  14. package/build/mailer/strategies/resend.strategy.js +48 -0
  15. package/build/mailer/strategies/smtp.strategy.d.ts +6 -0
  16. package/build/mailer/strategies/smtp.strategy.js +50 -0
  17. package/build/mailer/templates/layouts/main.hbs +103 -0
  18. package/build/mailer/templates/partials/footer.hbs +4 -0
  19. package/build/mailer/templates/partials/header.hbs +4 -0
  20. package/build/mailer/templates/views/account-update.hbs +20 -0
  21. package/build/mailer/templates/views/complaint-escalation.hbs +23 -0
  22. package/build/mailer/templates/views/invoice.hbs +29 -0
  23. package/build/mailer/templates/views/new-user.hbs +23 -0
  24. package/build/mailer/templates/views/organization-user-created.hbs +25 -0
  25. package/build/mailer/templates/views/reset-password.hbs +14 -0
  26. package/build/mailer/templates/views/verify-email.hbs +14 -0
  27. package/build/mailer/templates/views/welcome.hbs +22 -0
  28. package/build/middlewares/auth.middleware.js +2 -1
  29. package/build/middlewares/optional-auth.middleware.js +2 -1
  30. package/build/models/User.model.d.ts +2 -2
  31. package/build/models/User.model.js +23 -16
  32. package/build/models/contracts.model.d.ts +7 -0
  33. package/build/models/contracts.model.js +7 -0
  34. package/build/models/settings.model.d.ts +6 -0
  35. package/build/models/settings.model.js +6 -0
  36. package/build/services/pdf-templates/layouts/main.hbs +91 -0
  37. package/build/services/pdf-templates/partials/footer.hbs +4 -0
  38. package/build/services/pdf-templates/partials/header.hbs +4 -0
  39. package/build/services/pdf-templates/views/invoice.hbs +60 -0
  40. package/build/services/pdfGenerator.service.d.ts +16 -0
  41. package/build/services/pdfGenerator.service.js +198 -0
  42. package/build/services/rank.service.d.ts +2 -2
  43. package/build/types/JwtPayload.d.ts +2 -1
  44. package/build/types/User.d.ts +18 -7
  45. package/build/types/User.js +4 -0
  46. package/build/types/model-names.d.ts +1 -0
  47. package/build/types/model-names.js +1 -0
  48. package/build/types/pdf.types.d.ts +30 -0
  49. package/build/types/pdf.types.js +7 -0
  50. package/build/types/systemRoles.d.ts +1 -2
  51. package/build/types/systemRoles.js +3 -2
  52. package/build/utils/{bucket.d.ts → bucket-wasabi.d.ts} +10 -9
  53. package/build/utils/{bucket.js → bucket-wasabi.js} +102 -124
  54. package/build/utils/mask.d.ts +5 -0
  55. package/build/utils/mask.js +49 -0
  56. package/package.json +10 -2
@@ -12,29 +12,49 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.Bucket = void 0;
15
+ exports.bucketWasabi = void 0;
16
16
  const path_1 = __importDefault(require("path"));
17
17
  const aws_sdk_1 = __importDefault(require("aws-sdk"));
18
- class Bucket {
18
+ const redis_connection_1 = require("../middlewares/redis-connection");
19
+ class BucketWasabi {
19
20
  constructor() {
20
- this.s3 = new aws_sdk_1.default.S3({
21
- accessKeyId: process.env.BUCKET_ACESS_KEY,
22
- secretAccessKey: process.env.BUCKET_SECRET_KEY,
23
- region: process.env.BUCKET_REGION,
24
- });
25
- this.bucketName = process.env.BUCKET_NAME;
26
- this.rekognition = new aws_sdk_1.default.Rekognition({
27
- accessKeyId: process.env.BUCKET_ACESS_KEY,
28
- secretAccessKey: process.env.BUCKET_SECRET_KEY,
29
- region: process.env.BUCKET_REGION,
30
- });
21
+ this._s3 = null;
22
+ this._rekognition = null;
23
+ }
24
+ get s3() {
25
+ if (!this._s3) {
26
+ this._s3 = new aws_sdk_1.default.S3({
27
+ credentials: {
28
+ accessKeyId: process.env.WASABI_ACCESS_KEY,
29
+ secretAccessKey: process.env.WASABI_SECRET_KEY,
30
+ },
31
+ region: process.env.WASABI_REGION || 'us-east-1',
32
+ endpoint: process.env.WASABI_ENDPOINT ||
33
+ `https://s3.${process.env.WASABI_REGION || 'us-east-1'}.wasabisys.com`,
34
+ s3ForcePathStyle: true,
35
+ signatureVersion: 'v4',
36
+ });
37
+ }
38
+ return this._s3;
39
+ }
40
+ get rekognition() {
41
+ if (!this._rekognition) {
42
+ this._rekognition = new aws_sdk_1.default.Rekognition({
43
+ accessKeyId: process.env.BUCKET_ACESS_KEY,
44
+ secretAccessKey: process.env.BUCKET_SECRET_KEY,
45
+ region: process.env.BUCKET_REGION || 'us-east-1',
46
+ });
47
+ }
48
+ return this._rekognition;
49
+ }
50
+ get bucketName() {
51
+ return process.env.WASABI_BUCKET_NAME;
31
52
  }
32
53
  saveBucketFiles(folder, ...files) {
33
54
  return __awaiter(this, void 0, void 0, function* () {
34
55
  const CHUNK_SIZE = 20 * 1024 * 1024;
35
56
  const MAX_PARALLEL_FILES = 30;
36
57
  const MAX_RETRIES = 3;
37
- // Process files in parallel batches
38
58
  for (let i = 0; i < files.length; i += MAX_PARALLEL_FILES) {
39
59
  const fileBatch = files.slice(i, i + MAX_PARALLEL_FILES);
40
60
  yield Promise.all(fileBatch.map((file) => __awaiter(this, void 0, void 0, function* () {
@@ -45,14 +65,12 @@ class Bucket {
45
65
  }
46
66
  let multipartUpload;
47
67
  try {
48
- // Initiate multipart upload
49
68
  multipartUpload = yield new Promise((resolve, reject) => {
50
69
  this.s3.createMultipartUpload({
51
70
  Bucket: this.bucketName,
52
71
  Key: `${folder}/${file.filename}`,
53
72
  ContentType: contentType,
54
73
  ContentDisposition: 'inline',
55
- ServerSideEncryption: 'AES256',
56
74
  }, (err, data) => {
57
75
  if (err)
58
76
  reject(err);
@@ -62,13 +80,11 @@ class Bucket {
62
80
  });
63
81
  const uploadId = multipartUpload.UploadId;
64
82
  const parts = [];
65
- // Split buffer into chunks
66
83
  const buffer = file.buffer;
67
84
  const chunks = [];
68
85
  for (let i = 0; i < buffer.length; i += CHUNK_SIZE) {
69
86
  chunks.push(buffer.slice(i, i + CHUNK_SIZE));
70
87
  }
71
- // Upload chunks
72
88
  const partUploads = chunks.map((chunk, index) => {
73
89
  const partNumber = index + 1;
74
90
  const uploadChunkWithRetry = (...args_1) => __awaiter(this, [...args_1], void 0, function* (retryCount = 0) {
@@ -110,7 +126,6 @@ class Bucket {
110
126
  });
111
127
  const completedParts = yield Promise.all(partUploads);
112
128
  parts.push(...completedParts.sort((a, b) => { var _a, _b; return ((_a = a.PartNumber) !== null && _a !== void 0 ? _a : 0) - ((_b = b.PartNumber) !== null && _b !== void 0 ? _b : 0); }));
113
- // Complete multipart upload
114
129
  yield new Promise((resolve, reject) => {
115
130
  this.s3.completeMultipartUpload({
116
131
  Bucket: this.bucketName,
@@ -150,7 +165,6 @@ class Bucket {
150
165
  Body: file.buffer,
151
166
  ContentDisposition: 'inline',
152
167
  ContentType: contentType,
153
- ServerSideEncryption: 'AES256',
154
168
  }, (err, data) => {
155
169
  if (err)
156
170
  reject(err);
@@ -188,18 +202,45 @@ class Bucket {
188
202
  }
189
203
  return 'application/octet-stream';
190
204
  }
205
+ getPresignedUrl(fileKey) {
206
+ return __awaiter(this, void 0, void 0, function* () {
207
+ console.log('[getPresignedUrl] fileKey:', fileKey, '| bucket:', this.bucketName);
208
+ const redis = yield (0, redis_connection_1.getRedisClient)();
209
+ const cacheKey = `wasabi:presigned:${fileKey}`;
210
+ const cached = yield redis.get(cacheKey);
211
+ if (cached)
212
+ return cached;
213
+ const url = this.s3.getSignedUrl('getObject', {
214
+ Bucket: this.bucketName,
215
+ Key: fileKey,
216
+ Expires: 3600,
217
+ });
218
+ console.log('[getPresignedUrl] generated url:', url);
219
+ yield redis.set(cacheKey, url, 'EX', 3000);
220
+ return url;
221
+ });
222
+ }
223
+ // Fetches the image from Wasabi and returns it as bytes for Rekognition
224
+ getImageBytes(imageKey) {
225
+ return __awaiter(this, void 0, void 0, function* () {
226
+ const data = yield new Promise((resolve, reject) => {
227
+ this.s3.getObject({ Bucket: this.bucketName, Key: imageKey }, (err, data) => {
228
+ if (err)
229
+ reject(err);
230
+ else
231
+ resolve(data);
232
+ });
233
+ });
234
+ return data.Body;
235
+ });
236
+ }
191
237
  validateFace(imageKey) {
192
238
  return __awaiter(this, void 0, void 0, function* () {
193
239
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
194
240
  try {
195
- // Enhanced face detection with liveness checks
241
+ const imageBytes = yield this.getImageBytes(imageKey);
196
242
  const faceParams = {
197
- Image: {
198
- S3Object: {
199
- Bucket: this.bucketName,
200
- Name: imageKey,
201
- },
202
- },
243
+ Image: { Bytes: imageBytes },
203
244
  Attributes: ['ALL'],
204
245
  };
205
246
  const faceResult = yield this.rekognition.detectFaces(faceParams).promise();
@@ -219,25 +260,22 @@ class Bucket {
219
260
  };
220
261
  }
221
262
  const face = faceResult.FaceDetails[0];
222
- // Enhanced confidence check for liveness
223
263
  if (!face.Confidence || face.Confidence < 75) {
224
264
  return {
225
265
  isValid: false,
226
266
  error: {
227
267
  en: 'Image quality is not sufficient for verification',
228
- ar: 'جودة الصورة غير كافية للتحقق'
268
+ ar: 'جودة الصورة غير كافية للتحقق',
229
269
  },
230
270
  };
231
271
  }
232
- // Perform liveness detection using multiple AWS Rekognition features
233
- const livenessCheck = yield this.performLivenessDetection(imageKey);
272
+ const livenessCheck = yield this.performLivenessDetection(imageBytes);
234
273
  if (!livenessCheck.isLive) {
235
274
  return {
236
275
  isValid: false,
237
276
  error: livenessCheck.error,
238
277
  };
239
278
  }
240
- // Enhanced anti-spoofing checks
241
279
  const antiSpoofingCheck = yield this.performAntiSpoofingChecks(face);
242
280
  if (!antiSpoofingCheck.isValid) {
243
281
  return {
@@ -245,7 +283,6 @@ class Bucket {
245
283
  error: antiSpoofingCheck.error,
246
284
  };
247
285
  }
248
- // Original validation checks with enhanced thresholds
249
286
  if (((_a = face.Sunglasses) === null || _a === void 0 ? void 0 : _a.Value) || ((_b = face.Eyeglasses) === null || _b === void 0 ? void 0 : _b.Value)) {
250
287
  return {
251
288
  isValid: false,
@@ -255,7 +292,6 @@ class Bucket {
255
292
  },
256
293
  };
257
294
  }
258
- // Enhanced eye detection for liveness
259
295
  const eyesCheck = this.validateEyesForLiveness(face);
260
296
  if (!eyesCheck.isValid) {
261
297
  return {
@@ -269,7 +305,6 @@ class Bucket {
269
305
  error: { en: 'Mouth should be closed', ar: 'يجب أن يكون الفم مغلقاً' },
270
306
  };
271
307
  }
272
- // Enhanced pose validation for anti-spoofing
273
308
  const poseCheck = this.validatePoseForLiveness(face);
274
309
  if (!poseCheck.isValid) {
275
310
  return {
@@ -286,7 +321,6 @@ class Bucket {
286
321
  },
287
322
  };
288
323
  }
289
- // Reject excessive smiling which might indicate a photo
290
324
  if (((_j = face.Smile) === null || _j === void 0 ? void 0 : _j.Value) && ((_l = (_k = face.Smile) === null || _k === void 0 ? void 0 : _k.Confidence) !== null && _l !== void 0 ? _l : 0) > 80) {
291
325
  return {
292
326
  isValid: false,
@@ -307,21 +341,12 @@ class Bucket {
307
341
  }
308
342
  });
309
343
  }
310
- performLivenessDetection(imageKey) {
344
+ performLivenessDetection(imageBytes) {
311
345
  return __awaiter(this, void 0, void 0, function* () {
312
346
  try {
313
- // Use AWS Rekognition's text detection to check for printed photos
314
- const textParams = {
315
- Image: {
316
- S3Object: {
317
- Bucket: this.bucketName,
318
- Name: imageKey,
319
- },
320
- },
321
- };
322
- const textResult = yield this.rekognition.detectText(textParams).promise();
323
- // If significant text is detected around the face area, it might be a printed photo
324
- // Increased threshold to reduce false positives
347
+ const textResult = yield this.rekognition
348
+ .detectText({ Image: { Bytes: imageBytes } })
349
+ .promise();
325
350
  if (textResult.TextDetections && textResult.TextDetections.length > 8) {
326
351
  return {
327
352
  isLive: false,
@@ -331,23 +356,13 @@ class Bucket {
331
356
  },
332
357
  };
333
358
  }
334
- // Use label detection to identify screens, monitors, or printed materials
335
- const labelParams = {
336
- Image: {
337
- S3Object: {
338
- Bucket: this.bucketName,
339
- Name: imageKey,
340
- },
341
- },
342
- MaxLabels: 20,
343
- MinConfidence: 70,
344
- };
345
- const labelResult = yield this.rekognition.detectLabels(labelParams).promise();
359
+ const labelResult = yield this.rekognition
360
+ .detectLabels({ Image: { Bytes: imageBytes }, MaxLabels: 20, MinConfidence: 70 })
361
+ .promise();
346
362
  if (labelResult.Labels) {
347
- // Reduced list to only obvious screen/printed content and increased confidence threshold
348
363
  const suspiciousLabels = ['Screen', 'Monitor', 'Display', 'Computer Screen', 'Television'];
349
364
  for (const label of labelResult.Labels) {
350
- if (suspiciousLabels.some(suspicious => {
365
+ if (suspiciousLabels.some((suspicious) => {
351
366
  var _a, _b;
352
367
  return ((_a = label.Name) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes(suspicious.toLowerCase())) &&
353
368
  ((_b = label.Confidence) !== null && _b !== void 0 ? _b : 0) > 85;
@@ -366,8 +381,6 @@ class Bucket {
366
381
  }
367
382
  catch (error) {
368
383
  console.error('Error in liveness detection:', error);
369
- // Don't fail the entire validation for liveness detection errors
370
- // This prevents AWS service issues from blocking legitimate users
371
384
  return { isLive: true };
372
385
  }
373
386
  });
@@ -375,7 +388,6 @@ class Bucket {
375
388
  performAntiSpoofingChecks(face) {
376
389
  return __awaiter(this, void 0, void 0, function* () {
377
390
  try {
378
- // Check for overly perfect lighting (common in printed photos) - relaxed threshold
379
391
  const qualityMetrics = face.Quality;
380
392
  if ((qualityMetrics === null || qualityMetrics === void 0 ? void 0 : qualityMetrics.Brightness) && qualityMetrics.Brightness > 98) {
381
393
  return {
@@ -386,8 +398,8 @@ class Bucket {
386
398
  },
387
399
  };
388
400
  }
389
- // Check for suspicious sharpness levels - more lenient range
390
- if ((qualityMetrics === null || qualityMetrics === void 0 ? void 0 : qualityMetrics.Sharpness) && (qualityMetrics.Sharpness < 10 || qualityMetrics.Sharpness > 98)) {
401
+ if ((qualityMetrics === null || qualityMetrics === void 0 ? void 0 : qualityMetrics.Sharpness) &&
402
+ (qualityMetrics.Sharpness < 10 || qualityMetrics.Sharpness > 98)) {
391
403
  return {
392
404
  isValid: false,
393
405
  error: {
@@ -396,13 +408,14 @@ class Bucket {
396
408
  },
397
409
  };
398
410
  }
399
- // Enhanced landmark analysis for 3D face detection
400
411
  if (face.Landmarks && face.Landmarks.length > 0) {
401
412
  const landmarks = face.Landmarks;
402
- // Check for proper depth perception in landmarks
403
- const eyeLandmarks = landmarks.filter(l => l.Type === 'eyeLeft' || l.Type === 'eyeRight' ||
404
- l.Type === 'leftEyeLeft' || l.Type === 'leftEyeRight' ||
405
- l.Type === 'rightEyeLeft' || l.Type === 'rightEyeRight');
413
+ const eyeLandmarks = landmarks.filter((l) => l.Type === 'eyeLeft' ||
414
+ l.Type === 'eyeRight' ||
415
+ l.Type === 'leftEyeLeft' ||
416
+ l.Type === 'leftEyeRight' ||
417
+ l.Type === 'rightEyeLeft' ||
418
+ l.Type === 'rightEyeRight');
406
419
  if (eyeLandmarks.length < 4) {
407
420
  return {
408
421
  isValid: false,
@@ -412,24 +425,17 @@ class Bucket {
412
425
  },
413
426
  };
414
427
  }
415
- // Check for unnatural symmetry (printed photos often have perfect symmetry)
416
- const noseLandmarks = landmarks.filter(l => l.Type === 'nose' || l.Type === 'noseLeft' || l.Type === 'noseRight');
417
- if (noseLandmarks.length > 0) {
418
- // Additional geometric checks could be implemented here
419
- }
420
428
  }
421
429
  return { isValid: true };
422
430
  }
423
431
  catch (error) {
424
432
  console.error('Error in anti-spoofing checks:', error);
425
- // Don't fail validation for anti-spoofing errors to prevent service issues
426
433
  return { isValid: true };
427
434
  }
428
435
  });
429
436
  }
430
437
  validateEyesForLiveness(face) {
431
438
  var _a;
432
- // Enhanced eye validation for liveness detection - more lenient
433
439
  const eyesOpen = face.EyesOpen;
434
440
  if (!(eyesOpen === null || eyesOpen === void 0 ? void 0 : eyesOpen.Value) || ((_a = eyesOpen.Confidence) !== null && _a !== void 0 ? _a : 0) < 70) {
435
441
  return {
@@ -440,9 +446,8 @@ class Bucket {
440
446
  },
441
447
  };
442
448
  }
443
- // Check for natural eye appearance (avoid glass reflections, red-eye, etc.)
444
449
  if (face.Landmarks) {
445
- const eyeLandmarks = face.Landmarks.filter(l => { var _a, _b; return ((_a = l.Type) === null || _a === void 0 ? void 0 : _a.includes('eye')) || ((_b = l.Type) === null || _b === void 0 ? void 0 : _b.includes('Eye')); });
450
+ const eyeLandmarks = face.Landmarks.filter((l) => { var _a, _b; return ((_a = l.Type) === null || _a === void 0 ? void 0 : _a.includes('eye')) || ((_b = l.Type) === null || _b === void 0 ? void 0 : _b.includes('Eye')); });
446
451
  if (eyeLandmarks.length < 6) {
447
452
  return {
448
453
  isValid: false,
@@ -457,7 +462,7 @@ class Bucket {
457
462
  }
458
463
  validatePoseForLiveness(face) {
459
464
  const pose = face.Pose;
460
- const poseThreshold = 12; // Stricter threshold for liveness
465
+ const poseThreshold = 12;
461
466
  if (Math.abs((pose === null || pose === void 0 ? void 0 : pose.Pitch) || 0) > poseThreshold ||
462
467
  Math.abs((pose === null || pose === void 0 ? void 0 : pose.Roll) || 0) > poseThreshold ||
463
468
  Math.abs((pose === null || pose === void 0 ? void 0 : pose.Yaw) || 0) > poseThreshold) {
@@ -469,14 +474,8 @@ class Bucket {
469
474
  },
470
475
  };
471
476
  }
472
- // Additional check for unnatural pose stability (printed photos have perfect stability)
473
- // This would require multiple frames in a video-based solution, but we can check for other indicators
474
477
  return { isValid: true };
475
478
  }
476
- /**
477
- * Comprehensive face validation with advanced liveness detection
478
- * This method provides additional verification by analyzing multiple image properties
479
- */
480
479
  validateFaceWithAdvancedLiveness(imageKey) {
481
480
  return __awaiter(this, void 0, void 0, function* () {
482
481
  var _a, _b, _c;
@@ -489,34 +488,18 @@ class Bucket {
489
488
  error: baseValidation.error,
490
489
  };
491
490
  }
492
- // Calculate liveness score based on multiple factors
493
491
  let livenessScore = 100;
494
492
  const livenessIndicators = [];
495
493
  const spoofingRisks = [];
496
- // Get detailed face analysis
497
- const faceParams = {
498
- Image: {
499
- S3Object: {
500
- Bucket: this.bucketName,
501
- Name: imageKey,
502
- },
503
- },
504
- Attributes: ['ALL'],
505
- };
506
- const faceResult = yield this.rekognition.detectFaces(faceParams).promise();
494
+ const imageBytes = yield this.getImageBytes(imageKey);
495
+ const faceResult = yield this.rekognition
496
+ .detectFaces({ Image: { Bytes: imageBytes }, Attributes: ['ALL'] })
497
+ .promise();
507
498
  const face = faceResult.FaceDetails[0];
508
- // Analyze image metadata for camera vs screenshot indicators
509
499
  try {
510
- const moderationParams = {
511
- Image: {
512
- S3Object: {
513
- Bucket: this.bucketName,
514
- Name: imageKey,
515
- },
516
- },
517
- };
518
- const moderationResult = yield this.rekognition.detectModerationLabels(moderationParams).promise();
519
- // Check for any labels that might indicate artificial content
500
+ const moderationResult = yield this.rekognition
501
+ .detectModerationLabels({ Image: { Bytes: imageBytes } })
502
+ .promise();
520
503
  if (moderationResult.ModerationLabels && moderationResult.ModerationLabels.length > 0) {
521
504
  for (const label of moderationResult.ModerationLabels) {
522
505
  if (((_a = label.Name) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes('graphic')) && ((_b = label.Confidence) !== null && _b !== void 0 ? _b : 0) > 50) {
@@ -527,10 +510,8 @@ class Bucket {
527
510
  }
528
511
  }
529
512
  catch (error) {
530
- // Moderation check failed, but don't fail the entire validation
531
513
  console.warn('Moderation check failed:', error);
532
514
  }
533
- // Quality metrics analysis
534
515
  const quality = face.Quality;
535
516
  if (quality) {
536
517
  if (quality.Brightness && (quality.Brightness < 30 || quality.Brightness > 90)) {
@@ -548,7 +529,6 @@ class Bucket {
548
529
  spoofingRisks.push('Suspicious image sharpness');
549
530
  }
550
531
  }
551
- // Face feature confidence analysis
552
532
  if (face.Confidence && face.Confidence > 95) {
553
533
  livenessIndicators.push('High face detection confidence');
554
534
  }
@@ -556,11 +536,9 @@ class Bucket {
556
536
  livenessScore -= 20;
557
537
  spoofingRisks.push('Low face detection confidence');
558
538
  }
559
- // Eye analysis for natural appearance
560
539
  if (((_c = face.EyesOpen) === null || _c === void 0 ? void 0 : _c.Confidence) && face.EyesOpen.Confidence > 95) {
561
540
  livenessIndicators.push('Natural eye appearance');
562
541
  }
563
- // Pose analysis for natural positioning
564
542
  const pose = face.Pose;
565
543
  if (pose) {
566
544
  const totalPoseDeviation = Math.abs(pose.Pitch || 0) + Math.abs(pose.Roll || 0) + Math.abs(pose.Yaw || 0);
@@ -572,7 +550,6 @@ class Bucket {
572
550
  spoofingRisks.push('Unnaturally perfect pose alignment');
573
551
  }
574
552
  }
575
- // Landmark analysis for 3D facial structure
576
553
  if (face.Landmarks && face.Landmarks.length >= 15) {
577
554
  livenessIndicators.push('Comprehensive facial landmarks detected');
578
555
  }
@@ -580,15 +557,16 @@ class Bucket {
580
557
  livenessScore -= 10;
581
558
  spoofingRisks.push('Insufficient facial landmarks');
582
559
  }
583
- // Final liveness assessment
584
560
  const isLive = livenessScore >= 70;
585
561
  return {
586
562
  isValid: isLive,
587
563
  livenessScore,
588
- error: isLive ? undefined : {
589
- en: `Liveness verification failed (Score: ${livenessScore}/100). Please use a live camera capture`,
590
- ar: `فشل التحقق من الحيوية (النتيجة: ${livenessScore}/100). يرجى استخدام التقاط مباشر من الكاميرا`,
591
- },
564
+ error: isLive
565
+ ? undefined
566
+ : {
567
+ en: `Liveness verification failed (Score: ${livenessScore}/100). Please use a live camera capture`,
568
+ ar: `فشل التحقق من الحيوية (النتيجة: ${livenessScore}/100). يرجى استخدام التقاط مباشر من الكاميرا`,
569
+ },
592
570
  details: {
593
571
  faceConfidence: face.Confidence || 0,
594
572
  livenessIndicators,
@@ -607,7 +585,6 @@ class Bucket {
607
585
  });
608
586
  }
609
587
  }
610
- exports.Bucket = Bucket;
611
588
  const MIME_TYPES = {
612
589
  video: {
613
590
  '.mp4': 'video/mp4',
@@ -642,3 +619,4 @@ const MIME_TYPES = {
642
619
  '.pdf': 'application/pdf',
643
620
  },
644
621
  };
622
+ exports.bucketWasabi = new BucketWasabi();
@@ -0,0 +1,5 @@
1
+ import { PartyDetails } from '../types/pdf.types';
2
+ export declare const maskEmail: (email?: string) => string | undefined;
3
+ export declare const maskPhone: (phone?: string) => string | undefined;
4
+ export declare const maskName: (name?: string) => string | undefined;
5
+ export declare const maskParty: (party?: PartyDetails) => PartyDetails | undefined;
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.maskParty = exports.maskName = exports.maskPhone = exports.maskEmail = void 0;
4
+ const maskEmail = (email) => {
5
+ if (!email)
6
+ return undefined;
7
+ const [local, domain] = email.split('@');
8
+ if (!local || !domain)
9
+ return email;
10
+ const dotIdx = domain.lastIndexOf('.');
11
+ const tld = dotIdx >= 0 ? domain.slice(dotIdx) : '';
12
+ const domainHead = dotIdx >= 0 ? domain.slice(0, dotIdx) : domain;
13
+ const maskedLocal = `${local[0]}${'*'.repeat(Math.max(local.length - 1, 2))}`;
14
+ const maskedDomainHead = `${domainHead[0]}${'*'.repeat(Math.max(domainHead.length - 1, 2))}`;
15
+ return `${maskedLocal}@${maskedDomainHead}${tld}`;
16
+ };
17
+ exports.maskEmail = maskEmail;
18
+ const maskPhone = (phone) => {
19
+ if (!phone)
20
+ return undefined;
21
+ const digits = phone.replace(/\D/g, '');
22
+ if (digits.length < 6)
23
+ return phone;
24
+ const prefix = phone.startsWith('+') ? '+' : '';
25
+ const head = digits.slice(0, 4);
26
+ const tail = digits.slice(-2);
27
+ const maskedMiddle = '*'.repeat(digits.length - 6);
28
+ return `${prefix}${head}${maskedMiddle}${tail}`;
29
+ };
30
+ exports.maskPhone = maskPhone;
31
+ const maskName = (name) => {
32
+ if (!name)
33
+ return undefined;
34
+ const parts = name.trim().split(/\s+/);
35
+ if (parts.length === 1)
36
+ return parts[0];
37
+ return `${parts[0]} ${parts[1][0]}.`;
38
+ };
39
+ exports.maskName = maskName;
40
+ const maskParty = (party) => {
41
+ if (!party)
42
+ return undefined;
43
+ return {
44
+ name: (0, exports.maskName)(party.name),
45
+ email: (0, exports.maskEmail)(party.email),
46
+ phone: (0, exports.maskPhone)(party.phone),
47
+ };
48
+ };
49
+ exports.maskParty = maskParty;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duvdu-v1/duvdu",
3
- "version": "1.1.355",
3
+ "version": "1.1.360",
4
4
  "main": "./build/index.js",
5
5
  "types": "./build/index.d.ts",
6
6
  "files": [
@@ -8,7 +8,8 @@
8
8
  ],
9
9
  "scripts": {
10
10
  "clean": "rimraf ./build",
11
- "build": "npm run clean && tsc",
11
+ "build": "npm run clean && tsc && npm run copy-templates",
12
+ "copy-templates": "cp -R src/services/pdf-templates ./build/services/ && cp -R src/mailer/templates ./build/mailer/",
12
13
  "fix:build": "mv ./build/common/src/* ./build && rm -rf ./build/auth ./build/common",
13
14
  "pub": "git add . && git commit -m \"updates\" && npm version patch && npm run build && npm publish",
14
15
  "lint": "eslint .",
@@ -33,12 +34,16 @@
33
34
  "express-async-errors": "^3.1.1",
34
35
  "express-session": "^1.18.0",
35
36
  "express-validator": "^7.0.1",
37
+ "handlebars": "^4.7.9",
36
38
  "ioredis": "^5.6.1",
37
39
  "jsonwebtoken": "^9.0.2",
38
40
  "mongoose": "^8.0.4",
39
41
  "multer": "^1.4.5-lts.1",
40
42
  "node-nats-streaming": "^0.3.2",
43
+ "nodemailer": "^8.0.5",
44
+ "puppeteer": "^23.11.1",
41
45
  "redis": "^4.6.13",
46
+ "resend": "^6.12.0",
42
47
  "rimraf": "^5.0.5",
43
48
  "typescript": "^5.3.3",
44
49
  "uuid": "^9.0.1",
@@ -46,12 +51,15 @@
46
51
  "winston-daily-rotate-file": "^5.0.0"
47
52
  },
48
53
  "devDependencies": {
54
+ "@types/nodemailer": "^8.0.0",
49
55
  "@types/uuid": "^9.0.8",
50
56
  "del-cli": "^5.1.0",
51
57
  "eslint": "^8.56.0",
52
58
  "eslint-config-prettier": "^9.1.0",
53
59
  "eslint-plugin-import": "^2.29.1",
54
60
  "eslint-plugin-prettier": "^5.1.3",
61
+ "i": "^0.3.7",
62
+ "npm": "^11.12.1",
55
63
  "prettier": "^3.2.4"
56
64
  },
57
65
  "description": ""