@duvdu-v1/duvdu 1.1.331 → 1.1.334

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.
@@ -47,11 +47,17 @@ const isauthenticated = (req, res, next) => __awaiter(void 0, void 0, void 0, fu
47
47
  try {
48
48
  payload = (0, jsonwebtoken_1.verify)(req.session.access, process.env.JWT_KEY);
49
49
  req.loggedUser = payload;
50
- if (req.loggedUser.isBlocked.value)
50
+ const user = yield User_model_1.Users.findById(payload.id);
51
+ if (!user) {
52
+ return next(new unauthorized_error_1.UnauthorizedError({ en: 'user not found', ar: 'لا يوجد مستخدم' }, req.lang));
53
+ }
54
+ if (user.isBlocked.value)
51
55
  return next(new unauthorized_error_1.UnauthorizedError({
52
56
  en: `Forbidden: User is blocked ${req.loggedUser.isBlocked.reason}`,
53
57
  ar: ` ممنوع: المستخدم محظور ${req.loggedUser.isBlocked.reason}`,
54
58
  }, req.lang));
59
+ if (user.isDeleted)
60
+ return next(new unauthorized_error_1.UnauthorizedError({ en: 'user not found', ar: 'لا يوجد مستخدم' }, req.lang));
55
61
  next();
56
62
  }
57
63
  catch (error) {
@@ -11,7 +11,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.optionalAuthenticated = void 0;
13
13
  const jsonwebtoken_1 = require("jsonwebtoken");
14
+ const auth_middleware_1 = require("./auth.middleware");
14
15
  const unauthorized_error_1 = require("../errors/unauthorized-error");
16
+ const Role_model_1 = require("../models/Role.model");
17
+ const User_model_1 = require("../models/User.model");
15
18
  const optionalAuthenticated = (req, res, next) => __awaiter(void 0, void 0, void 0, function* () {
16
19
  if (!req.session.access)
17
20
  return next();
@@ -19,17 +22,56 @@ const optionalAuthenticated = (req, res, next) => __awaiter(void 0, void 0, void
19
22
  try {
20
23
  payload = (0, jsonwebtoken_1.verify)(req.session.access, process.env.JWT_KEY);
21
24
  req.loggedUser = payload;
22
- if (req.loggedUser.isBlocked.value)
25
+ const user = yield User_model_1.Users.findById(payload.id);
26
+ if (!user) {
27
+ return next(new unauthorized_error_1.UnauthorizedError({ en: 'user not found', ar: 'لا يوجد مستخدم' }, req.lang));
28
+ }
29
+ if (user.isBlocked.value)
23
30
  return next(new unauthorized_error_1.UnauthorizedError({
24
31
  en: `Forbidden: User is blocked ${req.loggedUser.isBlocked.reason}`,
25
32
  ar: ` ممنوع: المستخدم محظور ${req.loggedUser.isBlocked.reason}`,
26
33
  }, req.lang));
27
- if (req.loggedUser.isDeleted)
34
+ if (user.isDeleted)
28
35
  return next(new unauthorized_error_1.UnauthorizedError({ en: 'user not found', ar: 'لا يوجد مستخدم' }, req.lang));
36
+ next();
29
37
  }
30
38
  catch (error) {
31
- return next();
39
+ try {
40
+ const payload = (0, jsonwebtoken_1.verify)(req.session.refresh, process.env.JWT_KEY);
41
+ const user = yield User_model_1.Users.findOne({
42
+ _id: payload.id,
43
+ refreshTokens: {
44
+ $elemMatch: {
45
+ token: req.session.refresh,
46
+ },
47
+ },
48
+ });
49
+ if (!user)
50
+ return res.status(423).json({ message: 'token expired' });
51
+ if (user.isBlocked.value)
52
+ return next(new unauthorized_error_1.UnauthorizedError({
53
+ en: `Forbidden: User is blocked ${req.loggedUser.isBlocked.reason}`,
54
+ ar: ` ممنوع: المستخدم محظور ${req.loggedUser.isBlocked.reason}`,
55
+ }, req.lang));
56
+ if (user.isDeleted)
57
+ return next(new unauthorized_error_1.UnauthorizedError({ en: 'user not found', ar: 'لا يوجد مستخدم' }, req.lang));
58
+ const role = yield Role_model_1.Roles.findById(user.role);
59
+ if (!role)
60
+ return res.status(423).json({ message: 'invalid role' });
61
+ const accessToken = (0, auth_middleware_1.generateAccessToken)({
62
+ id: user.id,
63
+ isVerified: user.isVerified,
64
+ isBlocked: user.isBlocked,
65
+ role: { key: role.key, permissions: role.permissions },
66
+ });
67
+ req.session.access = accessToken;
68
+ req.session.refresh = req.session.refresh;
69
+ req.loggedUser = (0, jsonwebtoken_1.verify)(accessToken, process.env.JWT_KEY);
70
+ next();
71
+ }
72
+ catch (error) {
73
+ return res.status(423).json({ message: 'token expired' });
74
+ }
32
75
  }
33
- next();
34
76
  });
35
77
  exports.optionalAuthenticated = optionalAuthenticated;
@@ -15,4 +15,25 @@ export declare class Bucket {
15
15
  ar: string;
16
16
  };
17
17
  }>;
18
+ private performLivenessDetection;
19
+ private performAntiSpoofingChecks;
20
+ private validateEyesForLiveness;
21
+ private validatePoseForLiveness;
22
+ /**
23
+ * Comprehensive face validation with advanced liveness detection
24
+ * This method provides additional verification by analyzing multiple image properties
25
+ */
26
+ validateFaceWithAdvancedLiveness(imageKey: string): Promise<{
27
+ isValid: boolean;
28
+ livenessScore: number;
29
+ error?: {
30
+ en: string;
31
+ ar: string;
32
+ };
33
+ details?: {
34
+ faceConfidence: number;
35
+ livenessIndicators: string[];
36
+ spoofingRisks: string[];
37
+ };
38
+ }>;
18
39
  }
@@ -192,7 +192,8 @@ class Bucket {
192
192
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
193
193
  return __awaiter(this, void 0, void 0, function* () {
194
194
  try {
195
- const params = {
195
+ // Enhanced face detection with liveness checks
196
+ const faceParams = {
196
197
  Image: {
197
198
  S3Object: {
198
199
  Bucket: this.bucketName,
@@ -201,14 +202,14 @@ class Bucket {
201
202
  },
202
203
  Attributes: ['ALL'],
203
204
  };
204
- const result = yield this.rekognition.detectFaces(params).promise();
205
- if (!result.FaceDetails || result.FaceDetails.length === 0) {
205
+ const faceResult = yield this.rekognition.detectFaces(faceParams).promise();
206
+ if (!faceResult.FaceDetails || faceResult.FaceDetails.length === 0) {
206
207
  return {
207
208
  isValid: false,
208
209
  error: { en: 'No face detected in the image', ar: 'لا يوجد وجه مكتشف في الصورة' },
209
210
  };
210
211
  }
211
- if (result.FaceDetails.length > 1) {
212
+ if (faceResult.FaceDetails.length > 1) {
212
213
  return {
213
214
  isValid: false,
214
215
  error: {
@@ -217,53 +218,66 @@ class Bucket {
217
218
  },
218
219
  };
219
220
  }
220
- const face = result.FaceDetails[0];
221
- if (!face.Confidence || face.Confidence < 85) {
221
+ const face = faceResult.FaceDetails[0];
222
+ // Enhanced confidence check for liveness
223
+ if (!face.Confidence || face.Confidence < 90) {
222
224
  return {
223
225
  isValid: false,
224
- error: { en: 'Image quality is not sufficient', ar: 'جودة الصورة غير كافية' },
226
+ error: {
227
+ en: 'Image quality is not sufficient for verification',
228
+ ar: 'جودة الصورة غير كافية للتحقق'
229
+ },
230
+ };
231
+ }
232
+ // Perform liveness detection using multiple AWS Rekognition features
233
+ const livenessCheck = yield this.performLivenessDetection(imageKey);
234
+ if (!livenessCheck.isLive) {
235
+ return {
236
+ isValid: false,
237
+ error: livenessCheck.error,
238
+ };
239
+ }
240
+ // Enhanced anti-spoofing checks
241
+ const antiSpoofingCheck = yield this.performAntiSpoofingChecks(face);
242
+ if (!antiSpoofingCheck.isValid) {
243
+ return {
244
+ isValid: false,
245
+ error: antiSpoofingCheck.error,
225
246
  };
226
247
  }
248
+ // Original validation checks with enhanced thresholds
227
249
  if (((_a = face.Sunglasses) === null || _a === void 0 ? void 0 : _a.Value) || ((_b = face.Eyeglasses) === null || _b === void 0 ? void 0 : _b.Value)) {
228
250
  return {
229
251
  isValid: false,
230
252
  error: {
231
- en: 'Please remove glasses or sunglasses',
232
- ar: 'يرجى إزالة النظارات أو النظارات الشمسية',
253
+ en: 'Please remove glasses or sunglasses for verification',
254
+ ar: 'يرجى إزالة النظارات أو النظارات الشمسية للتحقق',
233
255
  },
234
256
  };
235
257
  }
236
- const leftEyeOpen = ((_c = face.EyesOpen) === null || _c === void 0 ? void 0 : _c.Value) && ((_e = (_d = face.EyesOpen) === null || _d === void 0 ? void 0 : _d.Confidence) !== null && _e !== void 0 ? _e : 0) > 85;
237
- const rightEyeOpen = ((_f = face.EyesOpen) === null || _f === void 0 ? void 0 : _f.Value) && ((_h = (_g = face.EyesOpen) === null || _g === void 0 ? void 0 : _g.Confidence) !== null && _h !== void 0 ? _h : 0) > 85;
238
- if (!leftEyeOpen || !rightEyeOpen) {
258
+ // Enhanced eye detection for liveness
259
+ const eyesCheck = this.validateEyesForLiveness(face);
260
+ if (!eyesCheck.isValid) {
239
261
  return {
240
262
  isValid: false,
241
- error: {
242
- en: 'Eyes must be fully open and clearly visible',
243
- ar: 'يجب أن تكون العينين مفتوحتين بالكامل وواضحتين',
244
- },
263
+ error: eyesCheck.error,
245
264
  };
246
265
  }
247
- if ((_j = face.MouthOpen) === null || _j === void 0 ? void 0 : _j.Value) {
266
+ if (((_c = face.MouthOpen) === null || _c === void 0 ? void 0 : _c.Value) && ((_e = (_d = face.MouthOpen) === null || _d === void 0 ? void 0 : _d.Confidence) !== null && _e !== void 0 ? _e : 0) > 70) {
248
267
  return {
249
268
  isValid: false,
250
269
  error: { en: 'Mouth should be closed', ar: 'يجب أن يكون الفم مغلقاً' },
251
270
  };
252
271
  }
253
- const pose = face.Pose;
254
- const poseThreshold = 15;
255
- if (Math.abs((pose === null || pose === void 0 ? void 0 : pose.Pitch) || 0) > poseThreshold ||
256
- Math.abs((pose === null || pose === void 0 ? void 0 : pose.Roll) || 0) > poseThreshold ||
257
- Math.abs((pose === null || pose === void 0 ? void 0 : pose.Yaw) || 0) > poseThreshold) {
272
+ // Enhanced pose validation for anti-spoofing
273
+ const poseCheck = this.validatePoseForLiveness(face);
274
+ if (!poseCheck.isValid) {
258
275
  return {
259
276
  isValid: false,
260
- error: {
261
- en: 'Face must be directly facing the camera',
262
- ar: 'يجب أن يكون الوجه مواجهاً للكاميرا مباشرة',
263
- },
277
+ error: poseCheck.error,
264
278
  };
265
279
  }
266
- if ((_k = face.FaceOccluded) === null || _k === void 0 ? void 0 : _k.Value) {
280
+ if (((_f = face.FaceOccluded) === null || _f === void 0 ? void 0 : _f.Value) && ((_h = (_g = face.FaceOccluded) === null || _g === void 0 ? void 0 : _g.Confidence) !== null && _h !== void 0 ? _h : 0) > 50) {
267
281
  return {
268
282
  isValid: false,
269
283
  error: {
@@ -272,7 +286,8 @@ class Bucket {
272
286
  },
273
287
  };
274
288
  }
275
- if ((_l = face.Smile) === null || _l === void 0 ? void 0 : _l.Value) {
289
+ // Reject excessive smiling which might indicate a photo
290
+ 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) {
276
291
  return {
277
292
  isValid: false,
278
293
  error: {
@@ -292,6 +307,301 @@ class Bucket {
292
307
  }
293
308
  });
294
309
  }
310
+ performLivenessDetection(imageKey) {
311
+ return __awaiter(this, void 0, void 0, function* () {
312
+ 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 text is detected around the face area, it might be a printed photo
324
+ if (textResult.TextDetections && textResult.TextDetections.length > 3) {
325
+ return {
326
+ isLive: false,
327
+ error: {
328
+ en: 'Static image detected. Please use a live camera capture',
329
+ ar: 'تم اكتشاف صورة ثابتة. يرجى استخدام التقاط مباشر من الكاميرا',
330
+ },
331
+ };
332
+ }
333
+ // Use label detection to identify screens, monitors, or printed materials
334
+ const labelParams = {
335
+ Image: {
336
+ S3Object: {
337
+ Bucket: this.bucketName,
338
+ Name: imageKey,
339
+ },
340
+ },
341
+ MaxLabels: 20,
342
+ MinConfidence: 70,
343
+ };
344
+ const labelResult = yield this.rekognition.detectLabels(labelParams).promise();
345
+ if (labelResult.Labels) {
346
+ const suspiciousLabels = ['Screen', 'Monitor', 'Display', 'Computer', 'Laptop', 'Phone', 'Tablet', 'Paper', 'Document', 'Photo', 'Picture'];
347
+ for (const label of labelResult.Labels) {
348
+ if (suspiciousLabels.some(suspicious => {
349
+ var _a, _b;
350
+ return ((_a = label.Name) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes(suspicious.toLowerCase())) &&
351
+ ((_b = label.Confidence) !== null && _b !== void 0 ? _b : 0) > 75;
352
+ })) {
353
+ return {
354
+ isLive: false,
355
+ error: {
356
+ en: 'Please take a live photo, not from a screen or printed image',
357
+ ar: 'يرجى التقاط صورة مباشرة، وليس من شاشة أو صورة مطبوعة',
358
+ },
359
+ };
360
+ }
361
+ }
362
+ }
363
+ return { isLive: true };
364
+ }
365
+ catch (error) {
366
+ console.error('Error in liveness detection:', error);
367
+ // Don't fail the entire validation for liveness detection errors
368
+ return { isLive: true };
369
+ }
370
+ });
371
+ }
372
+ performAntiSpoofingChecks(face) {
373
+ return __awaiter(this, void 0, void 0, function* () {
374
+ try {
375
+ // Check for overly perfect lighting (common in printed photos)
376
+ const qualityMetrics = face.Quality;
377
+ if ((qualityMetrics === null || qualityMetrics === void 0 ? void 0 : qualityMetrics.Brightness) && qualityMetrics.Brightness > 95) {
378
+ return {
379
+ isValid: false,
380
+ error: {
381
+ en: 'Lighting appears artificial. Please use natural lighting',
382
+ ar: 'الإضاءة تبدو اصطناعية. يرجى استخدام الإضاءة الطبيعية',
383
+ },
384
+ };
385
+ }
386
+ // Check for suspicious sharpness levels (printed photos often have different sharpness)
387
+ if ((qualityMetrics === null || qualityMetrics === void 0 ? void 0 : qualityMetrics.Sharpness) && (qualityMetrics.Sharpness < 20 || qualityMetrics.Sharpness > 95)) {
388
+ return {
389
+ isValid: false,
390
+ error: {
391
+ en: 'Image sharpness indicates possible static image. Please retake with live camera',
392
+ ar: 'وضوح الصورة يشير إلى صورة ثابتة محتملة. يرجى إعادة التقاط بكاميرا مباشرة',
393
+ },
394
+ };
395
+ }
396
+ // Enhanced landmark analysis for 3D face detection
397
+ if (face.Landmarks && face.Landmarks.length > 0) {
398
+ const landmarks = face.Landmarks;
399
+ // Check for proper depth perception in landmarks
400
+ const eyeLandmarks = landmarks.filter(l => l.Type === 'eyeLeft' || l.Type === 'eyeRight' ||
401
+ l.Type === 'leftEyeLeft' || l.Type === 'leftEyeRight' ||
402
+ l.Type === 'rightEyeLeft' || l.Type === 'rightEyeRight');
403
+ if (eyeLandmarks.length < 4) {
404
+ return {
405
+ isValid: false,
406
+ error: {
407
+ en: 'Insufficient facial features detected for verification',
408
+ ar: 'ملامح الوجه المكتشفة غير كافية للتحقق',
409
+ },
410
+ };
411
+ }
412
+ // Check for unnatural symmetry (printed photos often have perfect symmetry)
413
+ const noseLandmarks = landmarks.filter(l => l.Type === 'nose' || l.Type === 'noseLeft' || l.Type === 'noseRight');
414
+ if (noseLandmarks.length > 0) {
415
+ // Additional geometric checks could be implemented here
416
+ }
417
+ }
418
+ return { isValid: true };
419
+ }
420
+ catch (error) {
421
+ console.error('Error in anti-spoofing checks:', error);
422
+ return { isValid: true }; // Don't fail validation for anti-spoofing errors
423
+ }
424
+ });
425
+ }
426
+ validateEyesForLiveness(face) {
427
+ var _a;
428
+ // Enhanced eye validation for liveness detection
429
+ const eyesOpen = face.EyesOpen;
430
+ if (!(eyesOpen === null || eyesOpen === void 0 ? void 0 : eyesOpen.Value) || ((_a = eyesOpen.Confidence) !== null && _a !== void 0 ? _a : 0) < 90) {
431
+ return {
432
+ isValid: false,
433
+ error: {
434
+ en: 'Eyes must be fully open and clearly visible',
435
+ ar: 'يجب أن تكون العينين مفتوحتين بالكامل وواضحتين',
436
+ },
437
+ };
438
+ }
439
+ // Check for natural eye appearance (avoid glass reflections, red-eye, etc.)
440
+ if (face.Landmarks) {
441
+ 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')); });
442
+ if (eyeLandmarks.length < 6) {
443
+ return {
444
+ isValid: false,
445
+ error: {
446
+ en: 'Eye features not clearly detected. Please ensure good lighting',
447
+ ar: 'ملامح العين غير واضحة. يرجى التأكد من الإضاءة الجيدة',
448
+ },
449
+ };
450
+ }
451
+ }
452
+ return { isValid: true };
453
+ }
454
+ validatePoseForLiveness(face) {
455
+ const pose = face.Pose;
456
+ const poseThreshold = 12; // Stricter threshold for liveness
457
+ if (Math.abs((pose === null || pose === void 0 ? void 0 : pose.Pitch) || 0) > poseThreshold ||
458
+ Math.abs((pose === null || pose === void 0 ? void 0 : pose.Roll) || 0) > poseThreshold ||
459
+ Math.abs((pose === null || pose === void 0 ? void 0 : pose.Yaw) || 0) > poseThreshold) {
460
+ return {
461
+ isValid: false,
462
+ error: {
463
+ en: 'Face must be directly facing the camera',
464
+ ar: 'يجب أن يكون الوجه مواجهاً للكاميرا مباشرة',
465
+ },
466
+ };
467
+ }
468
+ // Additional check for unnatural pose stability (printed photos have perfect stability)
469
+ // This would require multiple frames in a video-based solution, but we can check for other indicators
470
+ return { isValid: true };
471
+ }
472
+ /**
473
+ * Comprehensive face validation with advanced liveness detection
474
+ * This method provides additional verification by analyzing multiple image properties
475
+ */
476
+ validateFaceWithAdvancedLiveness(imageKey) {
477
+ var _a, _b, _c;
478
+ return __awaiter(this, void 0, void 0, function* () {
479
+ try {
480
+ const baseValidation = yield this.validateFace(imageKey);
481
+ if (!baseValidation.isValid) {
482
+ return {
483
+ isValid: false,
484
+ livenessScore: 0,
485
+ error: baseValidation.error,
486
+ };
487
+ }
488
+ // Calculate liveness score based on multiple factors
489
+ let livenessScore = 100;
490
+ const livenessIndicators = [];
491
+ const spoofingRisks = [];
492
+ // Get detailed face analysis
493
+ const faceParams = {
494
+ Image: {
495
+ S3Object: {
496
+ Bucket: this.bucketName,
497
+ Name: imageKey,
498
+ },
499
+ },
500
+ Attributes: ['ALL'],
501
+ };
502
+ const faceResult = yield this.rekognition.detectFaces(faceParams).promise();
503
+ const face = faceResult.FaceDetails[0];
504
+ // Analyze image metadata for camera vs screenshot indicators
505
+ try {
506
+ const moderationParams = {
507
+ Image: {
508
+ S3Object: {
509
+ Bucket: this.bucketName,
510
+ Name: imageKey,
511
+ },
512
+ },
513
+ };
514
+ const moderationResult = yield this.rekognition.detectModerationLabels(moderationParams).promise();
515
+ // Check for any labels that might indicate artificial content
516
+ if (moderationResult.ModerationLabels && moderationResult.ModerationLabels.length > 0) {
517
+ for (const label of moderationResult.ModerationLabels) {
518
+ if (((_a = label.Name) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes('graphic')) && ((_b = label.Confidence) !== null && _b !== void 0 ? _b : 0) > 50) {
519
+ livenessScore -= 20;
520
+ spoofingRisks.push('Artificial content detected');
521
+ }
522
+ }
523
+ }
524
+ }
525
+ catch (error) {
526
+ // Moderation check failed, but don't fail the entire validation
527
+ console.warn('Moderation check failed:', error);
528
+ }
529
+ // Quality metrics analysis
530
+ const quality = face.Quality;
531
+ if (quality) {
532
+ if (quality.Brightness && (quality.Brightness < 30 || quality.Brightness > 90)) {
533
+ livenessScore -= 10;
534
+ spoofingRisks.push('Unusual lighting conditions');
535
+ }
536
+ else {
537
+ livenessIndicators.push('Natural lighting detected');
538
+ }
539
+ if (quality.Sharpness && quality.Sharpness > 50 && quality.Sharpness < 90) {
540
+ livenessIndicators.push('Appropriate image sharpness');
541
+ }
542
+ else if (quality.Sharpness && (quality.Sharpness < 30 || quality.Sharpness > 95)) {
543
+ livenessScore -= 15;
544
+ spoofingRisks.push('Suspicious image sharpness');
545
+ }
546
+ }
547
+ // Face feature confidence analysis
548
+ if (face.Confidence && face.Confidence > 95) {
549
+ livenessIndicators.push('High face detection confidence');
550
+ }
551
+ else if (face.Confidence && face.Confidence < 85) {
552
+ livenessScore -= 20;
553
+ spoofingRisks.push('Low face detection confidence');
554
+ }
555
+ // Eye analysis for natural appearance
556
+ if (((_c = face.EyesOpen) === null || _c === void 0 ? void 0 : _c.Confidence) && face.EyesOpen.Confidence > 95) {
557
+ livenessIndicators.push('Natural eye appearance');
558
+ }
559
+ // Pose analysis for natural positioning
560
+ const pose = face.Pose;
561
+ if (pose) {
562
+ const totalPoseDeviation = Math.abs(pose.Pitch || 0) + Math.abs(pose.Roll || 0) + Math.abs(pose.Yaw || 0);
563
+ if (totalPoseDeviation > 0 && totalPoseDeviation < 25) {
564
+ livenessIndicators.push('Natural head pose variation');
565
+ }
566
+ else if (totalPoseDeviation === 0) {
567
+ livenessScore -= 25;
568
+ spoofingRisks.push('Unnaturally perfect pose alignment');
569
+ }
570
+ }
571
+ // Landmark analysis for 3D facial structure
572
+ if (face.Landmarks && face.Landmarks.length >= 15) {
573
+ livenessIndicators.push('Comprehensive facial landmarks detected');
574
+ }
575
+ else {
576
+ livenessScore -= 10;
577
+ spoofingRisks.push('Insufficient facial landmarks');
578
+ }
579
+ // Final liveness assessment
580
+ const isLive = livenessScore >= 70;
581
+ return {
582
+ isValid: isLive,
583
+ livenessScore,
584
+ error: isLive ? undefined : {
585
+ en: `Liveness verification failed (Score: ${livenessScore}/100). Please use a live camera capture`,
586
+ ar: `فشل التحقق من الحيوية (النتيجة: ${livenessScore}/100). يرجى استخدام التقاط مباشر من الكاميرا`,
587
+ },
588
+ details: {
589
+ faceConfidence: face.Confidence || 0,
590
+ livenessIndicators,
591
+ spoofingRisks,
592
+ },
593
+ };
594
+ }
595
+ catch (error) {
596
+ console.error('Error in advanced face validation:', error);
597
+ return {
598
+ isValid: false,
599
+ livenessScore: 0,
600
+ error: { en: 'Error processing image', ar: 'خطأ في معالجة الصورة' },
601
+ };
602
+ }
603
+ });
604
+ }
295
605
  }
296
606
  exports.Bucket = Bucket;
297
607
  const MIME_TYPES = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duvdu-v1/duvdu",
3
- "version": "1.1.331",
3
+ "version": "1.1.334",
4
4
  "main": "./build/index.js",
5
5
  "types": "./build/index.d.ts",
6
6
  "files": [