@fiado/type-kit 3.252.0 → 3.254.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.
- package/_test_/unit/biometrics/BiometricVerificationChangedV1.test.ts +96 -0
- package/_test_/unit/biometrics/CreateBiometricVerificationRequest.test.ts +109 -0
- package/bin/biometrics/dtos/BiometricDelivery.d.ts +65 -0
- package/bin/biometrics/dtos/BiometricDelivery.js +89 -0
- package/bin/biometrics/dtos/BiometricSubject.d.ts +43 -0
- package/bin/biometrics/dtos/BiometricSubject.js +63 -0
- package/bin/biometrics/dtos/requests/CreateBiometricVerificationRequest.d.ts +73 -0
- package/bin/biometrics/dtos/requests/CreateBiometricVerificationRequest.js +81 -0
- package/bin/biometrics/dtos/responses/BiometricVerificationResponse.d.ts +87 -0
- package/bin/biometrics/dtos/responses/BiometricVerificationResponse.js +131 -0
- package/bin/biometrics/enums/BiometricDeliveryModeEnum.d.ts +29 -0
- package/bin/biometrics/enums/BiometricDeliveryModeEnum.js +33 -0
- package/bin/biometrics/enums/BiometricEventTypeEnum.d.ts +28 -0
- package/bin/biometrics/enums/BiometricEventTypeEnum.js +32 -0
- package/bin/biometrics/enums/BiometricProviderEnum.d.ts +25 -0
- package/bin/biometrics/enums/BiometricProviderEnum.js +29 -0
- package/bin/biometrics/enums/BiometricResultEnum.d.ts +25 -0
- package/bin/biometrics/enums/BiometricResultEnum.js +29 -0
- package/bin/biometrics/enums/BiometricTypeEnum.d.ts +20 -0
- package/bin/biometrics/enums/BiometricTypeEnum.js +24 -0
- package/bin/biometrics/enums/BiometricVerificationStatusEnum.d.ts +33 -0
- package/bin/biometrics/enums/BiometricVerificationStatusEnum.js +37 -0
- package/bin/biometrics/events/BiometricVerificationChangedV1.d.ts +80 -0
- package/bin/biometrics/events/BiometricVerificationChangedV1.js +98 -0
- package/bin/biometrics/index.d.ts +11 -0
- package/bin/biometrics/index.js +35 -0
- package/bin/helpdesk/dtos/HelpdeskCreateTicketRequest.d.ts +6 -0
- package/bin/helpdesk/dtos/HelpdeskCreateTicketRequest.js +16 -0
- package/bin/index.d.ts +1 -0
- package/bin/index.js +4 -3
- package/package.json +1 -1
- package/src/biometrics/dtos/BiometricDelivery.ts +81 -0
- package/src/biometrics/dtos/BiometricSubject.ts +54 -0
- package/src/biometrics/dtos/requests/CreateBiometricVerificationRequest.ts +101 -0
- package/src/biometrics/dtos/responses/BiometricVerificationResponse.ts +133 -0
- package/src/biometrics/enums/BiometricDeliveryModeEnum.ts +31 -0
- package/src/biometrics/enums/BiometricEventTypeEnum.ts +31 -0
- package/src/biometrics/enums/BiometricProviderEnum.ts +28 -0
- package/src/biometrics/enums/BiometricResultEnum.ts +28 -0
- package/src/biometrics/enums/BiometricTypeEnum.ts +23 -0
- package/src/biometrics/enums/BiometricVerificationStatusEnum.ts +37 -0
- package/src/biometrics/events/BiometricVerificationChangedV1.ts +114 -0
- package/src/biometrics/index.ts +24 -0
- package/src/helpdesk/dtos/HelpdeskCreateTicketRequest.ts +17 -1
- package/src/index.ts +1 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { plainToInstance } from 'class-transformer';
|
|
3
|
+
import { validate } from 'class-validator';
|
|
4
|
+
import {
|
|
5
|
+
BiometricVerificationChangedV1,
|
|
6
|
+
BiometricEventTypeEnum,
|
|
7
|
+
BiometricProviderEnum,
|
|
8
|
+
BiometricResultEnum,
|
|
9
|
+
} from '../../../src/biometrics/index';
|
|
10
|
+
|
|
11
|
+
/** Base válida de un evento; cada caso pisa solo el campo que está probando. */
|
|
12
|
+
const base = {
|
|
13
|
+
eventId: 'evt-1',
|
|
14
|
+
biometricVerificationId: 'bio-1',
|
|
15
|
+
provider: 'METAMAP',
|
|
16
|
+
eventType: 'STEP_COMPLETED',
|
|
17
|
+
occurredAt: '2026-07-26T10:05:00.000Z',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const instancia = (raw: Record<string, unknown>): BiometricVerificationChangedV1 =>
|
|
21
|
+
plainToInstance(BiometricVerificationChangedV1, raw, { excludeExtraneousValues: true });
|
|
22
|
+
|
|
23
|
+
describe('BiometricVerificationChangedV1', () => {
|
|
24
|
+
it('valida el shape que publica el webhook desde un step_completed', async () => {
|
|
25
|
+
const dto = instancia({ ...base, result: 'MATCH', score: 92, providerRef: 'mm-abc' });
|
|
26
|
+
|
|
27
|
+
expect(await validate(dto as object)).toHaveLength(0);
|
|
28
|
+
expect(dto.eventType).toBe(BiometricEventTypeEnum.STEP_COMPLETED);
|
|
29
|
+
expect(dto.provider).toBe(BiometricProviderEnum.METAMAP);
|
|
30
|
+
expect(dto.result).toBe(BiometricResultEnum.MATCH);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('valida sin result ni score (VERIFICATION_STARTED no los trae)', async () => {
|
|
34
|
+
const dto = instancia({ ...base, eventType: 'VERIFICATION_STARTED' });
|
|
35
|
+
|
|
36
|
+
expect(await validate(dto as object)).toHaveLength(0);
|
|
37
|
+
expect(dto.result).toBeUndefined();
|
|
38
|
+
expect(dto.score).toBeUndefined();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('eventId — la clave de deduplicación', () => {
|
|
42
|
+
it('rechaza el evento sin eventId', async () => {
|
|
43
|
+
const { eventId: _omitido, ...sinEventId } = base;
|
|
44
|
+
const errores = await validate(instancia(sinEventId) as object);
|
|
45
|
+
|
|
46
|
+
expect(errores.map((e) => e.property)).toContain('eventId');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('rechaza eventId vacío: pasaría el boundary y no deduplicaría nada', async () => {
|
|
50
|
+
const errores = await validate(instancia({ ...base, eventId: '' }) as object);
|
|
51
|
+
|
|
52
|
+
expect(errores.map((e) => e.property)).toContain('eventId');
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('occurredAt — el candado es LÉXICO, así que exige Z sin offset', () => {
|
|
57
|
+
it('acepta UTC con Z', async () => {
|
|
58
|
+
expect(await validate(instancia({ ...base, occurredAt: '2026-07-26T10:05:00.000Z' }) as object)).toHaveLength(0);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('acepta UTC con Z sin milisegundos', async () => {
|
|
62
|
+
expect(await validate(instancia({ ...base, occurredAt: '2026-07-26T10:05:00Z' }) as object)).toHaveLength(0);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 🔴 El caso que hace daño de verdad. `+05:00` son las 10:05 UTC, pero comparado como TEXTO es
|
|
67
|
+
* mayor que un `T10:06:00Z` posterior: el consumer lo leería como más nuevo, descartaría el
|
|
68
|
+
* evento real y la transición no se aplicaría nunca — sin un solo error en los logs.
|
|
69
|
+
*/
|
|
70
|
+
it('RECHAZA un offset, aunque sea ISO-8601 válido', async () => {
|
|
71
|
+
const errores = await validate(instancia({ ...base, occurredAt: '2026-07-26T15:05:00+05:00' }) as object);
|
|
72
|
+
|
|
73
|
+
expect(errores.map((e) => e.property)).toContain('occurredAt');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('rechaza una fecha sin hora (@IsISO8601 sola la aceptaría)', async () => {
|
|
77
|
+
const errores = await validate(instancia({ ...base, occurredAt: '2026-07-26' }) as object);
|
|
78
|
+
|
|
79
|
+
expect(errores.map((e) => e.property)).toContain('occurredAt');
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('score', () => {
|
|
84
|
+
it('rechaza un score fuera de 0–100', async () => {
|
|
85
|
+
const errores = await validate(instancia({ ...base, score: 101 }) as object);
|
|
86
|
+
|
|
87
|
+
expect(errores.map((e) => e.property)).toContain('score');
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('rechaza un eventType que no está en el vocabulario nuestro', async () => {
|
|
92
|
+
const errores = await validate(instancia({ ...base, eventType: 'verification_started' }) as object);
|
|
93
|
+
|
|
94
|
+
expect(errores.map((e) => e.property)).toContain('eventType');
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { plainToInstance } from 'class-transformer';
|
|
3
|
+
import { validate } from 'class-validator';
|
|
4
|
+
import {
|
|
5
|
+
CreateBiometricVerificationRequest,
|
|
6
|
+
BiometricProviderEnum,
|
|
7
|
+
BiometricTypeEnum,
|
|
8
|
+
} from '../../../src/biometrics/index';
|
|
9
|
+
|
|
10
|
+
/** El request mínimo válido: los 4 campos obligatorios. */
|
|
11
|
+
const base = {
|
|
12
|
+
type: 'FACEMATCH',
|
|
13
|
+
provider: 'METAMAP',
|
|
14
|
+
directoryId: 'dir-1',
|
|
15
|
+
requestedBy: 'surekeep-wizard',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const instancia = (raw: Record<string, unknown>): CreateBiometricVerificationRequest =>
|
|
19
|
+
plainToInstance(CreateBiometricVerificationRequest, raw, { excludeExtraneousValues: true });
|
|
20
|
+
|
|
21
|
+
const propiedadesConError = async (raw: Record<string, unknown>): Promise<string[]> =>
|
|
22
|
+
(await validate(instancia(raw) as object)).map((e) => e.property);
|
|
23
|
+
|
|
24
|
+
describe('CreateBiometricVerificationRequest', () => {
|
|
25
|
+
it('valida con solo los 4 obligatorios', async () => {
|
|
26
|
+
const dto = instancia(base);
|
|
27
|
+
|
|
28
|
+
expect(await validate(dto as object)).toHaveLength(0);
|
|
29
|
+
expect(dto.type).toBe(BiometricTypeEnum.FACEMATCH);
|
|
30
|
+
expect(dto.provider).toBe(BiometricProviderEnum.METAMAP);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('valida el request completo', async () => {
|
|
34
|
+
const dto = instancia({
|
|
35
|
+
...base,
|
|
36
|
+
subject: { peopleId: 'ppl-1', countryId: '484', phoneNumber: '+521234567890' },
|
|
37
|
+
referenceImageUrl: 'https://cdn.fiado.test/ref/abc.jpg',
|
|
38
|
+
ttlSeconds: 3600,
|
|
39
|
+
callerContext: 'sesion-42',
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
expect(await validate(dto as object)).toHaveLength(0);
|
|
43
|
+
expect(dto.subject?.countryId).toBe('484');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('los 4 obligatorios', () => {
|
|
47
|
+
it.each(['type', 'provider', 'directoryId', 'requestedBy'])('rechaza sin %s', async (campo) => {
|
|
48
|
+
const { [campo]: _omitido, ...incompleto } = base as Record<string, unknown>;
|
|
49
|
+
|
|
50
|
+
expect(await propiedadesConError(incompleto)).toContain(campo);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* `@IsNotEmpty()` y no solo `@IsString()`: un `''` pasaría el boundary y fallaría tres pasos río
|
|
55
|
+
* abajo, contra identity, con un error que no apunta a nada.
|
|
56
|
+
*/
|
|
57
|
+
it('rechaza directoryId vacío', async () => {
|
|
58
|
+
expect(await propiedadesConError({ ...base, directoryId: '' })).toContain('directoryId');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('provider es obligatorio — DEC-004', () => {
|
|
63
|
+
it('rechaza un provider fuera del enum', async () => {
|
|
64
|
+
expect(await propiedadesConError({ ...base, provider: 'FACETEC' })).toContain('provider');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* APPLE es un valor VÁLIDO del enum aunque no esté implementado: el rechazo por "no soportado"
|
|
69
|
+
* es del manager (`BIOMETRIC_NOT_SUPPORTED`, 422), no del DTO. Si el DTO lo rechazara, el caller
|
|
70
|
+
* recibiría un 400 de validación en vez de un 422 accionable.
|
|
71
|
+
*/
|
|
72
|
+
it('acepta APPLE en el DTO — el "no soportado" lo decide el manager, no la validación', async () => {
|
|
73
|
+
expect(await propiedadesConError({ ...base, provider: 'APPLE' })).toHaveLength(0);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('subject.phoneNumber exige E.164 — la normalización es del caller', () => {
|
|
78
|
+
it('acepta E.164', async () => {
|
|
79
|
+
expect(await propiedadesConError({ ...base, subject: { phoneNumber: '+521234567890' } })).toHaveLength(0);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* El wizard guarda 10 dígitos crudos en su sesión y solo los convierte al cruzar hacia el
|
|
84
|
+
* proveedor. Mandarlos pelados acá produciría una metadata que el proveedor acepta y que después
|
|
85
|
+
* nadie puede reconciliar.
|
|
86
|
+
*/
|
|
87
|
+
it('RECHAZA 10 dígitos pelados', async () => {
|
|
88
|
+
expect(await propiedadesConError({ ...base, subject: { phoneNumber: '1234567890' } })).toContain('subject');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('rechaza un teléfono con espacios', async () => {
|
|
92
|
+
expect(await propiedadesConError({ ...base, subject: { phoneNumber: '+52 123 456 7890' } })).toContain('subject');
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('ttlSeconds', () => {
|
|
97
|
+
it('rechaza un TTL mayor a 24 h: lo que se entrega es una identidad abierta', async () => {
|
|
98
|
+
expect(await propiedadesConError({ ...base, ttlSeconds: 86401 })).toContain('ttlSeconds');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('rechaza un TTL menor a 60 s', async () => {
|
|
102
|
+
expect(await propiedadesConError({ ...base, ttlSeconds: 59 })).toContain('ttlSeconds');
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('rechaza un callerContext de más de 256 caracteres', async () => {
|
|
107
|
+
expect(await propiedadesConError({ ...base, callerContext: 'x'.repeat(257) })).toContain('callerContext');
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lo que el caller necesita para que el biométrico se ejecute. **Su forma la determina
|
|
3
|
+
* `BiometricVerificationResponse.deliveryMode`, que es el discriminador.**
|
|
4
|
+
*
|
|
5
|
+
* Se modela como TRES clases + una unión de tipos en vez de un objeto con todo opcional. Con la
|
|
6
|
+
* unión, TypeScript obliga al caller a hacer el `switch (deliveryMode)` y sabe exactamente qué
|
|
7
|
+
* campos tiene en cada rama. Con un objeto plano de campos opcionales, el compilador no ayuda y el
|
|
8
|
+
* caller termina haciendo `link!` — que es el bug esperando a que entre el segundo proveedor.
|
|
9
|
+
*
|
|
10
|
+
* `biometrics-business` — Entrega 1 (solo `RedirectDelivery` produce valores reales).
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* `deliveryMode = REDIRECT`. El usuario abre el link y ejecuta el biométrico ahí.
|
|
14
|
+
* Es lo que produce Metamap: una URL hospedada, acortada, con TTL.
|
|
15
|
+
*/
|
|
16
|
+
export declare class RedirectDelivery {
|
|
17
|
+
/**
|
|
18
|
+
* La URL a abrir. Ya viene acortada — si el acortador no respondió, viene la larga.
|
|
19
|
+
* Que el acortador falle NO detiene la operación: un QR más denso se escanea igual.
|
|
20
|
+
*/
|
|
21
|
+
link: string;
|
|
22
|
+
/** Hasta cuándo sirve el link. ISO-8601 UTC con `Z`. */
|
|
23
|
+
expiresAt: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* `deliveryMode = CHALLENGE`. El biométrico corre en el dispositivo (Apple Face ID, biometría de
|
|
27
|
+
* Android) y el cliente devuelve una attestation firmada.
|
|
28
|
+
*
|
|
29
|
+
* ⚠️ **Declarado, sin implementar.** El endpoint que recibe la attestation
|
|
30
|
+
* (`POST /.../{id}/assertion`) queda fuera de la Entrega 1 a propósito — el contrato ya lo admite
|
|
31
|
+
* sin romperse, que es todo lo que hacía falta decidir ahora.
|
|
32
|
+
*/
|
|
33
|
+
export declare class ChallengeDelivery {
|
|
34
|
+
/** El desafío que el dispositivo debe firmar. */
|
|
35
|
+
challenge: string;
|
|
36
|
+
/** Nonce anti-replay. Un solo uso. */
|
|
37
|
+
nonce: string;
|
|
38
|
+
/** Hasta cuándo sirve el challenge. ISO-8601 UTC con `Z`. */
|
|
39
|
+
expiresAt: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* `deliveryMode = IMMEDIATE`. No hay nada que entregar: el biométrico ya corrió en el servidor
|
|
43
|
+
* (Amazon Rekognition) y el veredicto viene en `result` de la misma respuesta.
|
|
44
|
+
*
|
|
45
|
+
* Vacío a propósito. Existe para que la unión sea exhaustiva y el `switch` del caller no necesite
|
|
46
|
+
* un `default` que trague casos nuevos en silencio.
|
|
47
|
+
*
|
|
48
|
+
* ⚠️ Declarado, sin implementar.
|
|
49
|
+
*/
|
|
50
|
+
export declare class ImmediateDelivery {
|
|
51
|
+
/** Reservado. Nunca se llena hoy — existe para que la clase no sea estructuralmente vacía. */
|
|
52
|
+
note?: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* La unión. El caller discrimina con `deliveryMode`:
|
|
56
|
+
*
|
|
57
|
+
* ```ts
|
|
58
|
+
* switch (res.deliveryMode) {
|
|
59
|
+
* case BiometricDeliveryModeEnum.REDIRECT: abrir((res.delivery as RedirectDelivery).link); break;
|
|
60
|
+
* case BiometricDeliveryModeEnum.CHALLENGE: firmar(res.delivery as ChallengeDelivery); break;
|
|
61
|
+
* case BiometricDeliveryModeEnum.IMMEDIATE: leer(res.result); break;
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export type BiometricDelivery = RedirectDelivery | ChallengeDelivery | ImmediateDelivery;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.ImmediateDelivery = exports.ChallengeDelivery = exports.RedirectDelivery = void 0;
|
|
13
|
+
const class_transformer_1 = require("class-transformer");
|
|
14
|
+
const class_validator_1 = require("class-validator");
|
|
15
|
+
/**
|
|
16
|
+
* Lo que el caller necesita para que el biométrico se ejecute. **Su forma la determina
|
|
17
|
+
* `BiometricVerificationResponse.deliveryMode`, que es el discriminador.**
|
|
18
|
+
*
|
|
19
|
+
* Se modela como TRES clases + una unión de tipos en vez de un objeto con todo opcional. Con la
|
|
20
|
+
* unión, TypeScript obliga al caller a hacer el `switch (deliveryMode)` y sabe exactamente qué
|
|
21
|
+
* campos tiene en cada rama. Con un objeto plano de campos opcionales, el compilador no ayuda y el
|
|
22
|
+
* caller termina haciendo `link!` — que es el bug esperando a que entre el segundo proveedor.
|
|
23
|
+
*
|
|
24
|
+
* `biometrics-business` — Entrega 1 (solo `RedirectDelivery` produce valores reales).
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* `deliveryMode = REDIRECT`. El usuario abre el link y ejecuta el biométrico ahí.
|
|
28
|
+
* Es lo que produce Metamap: una URL hospedada, acortada, con TTL.
|
|
29
|
+
*/
|
|
30
|
+
class RedirectDelivery {
|
|
31
|
+
}
|
|
32
|
+
exports.RedirectDelivery = RedirectDelivery;
|
|
33
|
+
__decorate([
|
|
34
|
+
(0, class_transformer_1.Expose)(),
|
|
35
|
+
(0, class_validator_1.IsString)(),
|
|
36
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
37
|
+
__metadata("design:type", String)
|
|
38
|
+
], RedirectDelivery.prototype, "link", void 0);
|
|
39
|
+
__decorate([
|
|
40
|
+
(0, class_transformer_1.Expose)(),
|
|
41
|
+
(0, class_validator_1.IsISO8601)({ strict: true }),
|
|
42
|
+
__metadata("design:type", String)
|
|
43
|
+
], RedirectDelivery.prototype, "expiresAt", void 0);
|
|
44
|
+
/**
|
|
45
|
+
* `deliveryMode = CHALLENGE`. El biométrico corre en el dispositivo (Apple Face ID, biometría de
|
|
46
|
+
* Android) y el cliente devuelve una attestation firmada.
|
|
47
|
+
*
|
|
48
|
+
* ⚠️ **Declarado, sin implementar.** El endpoint que recibe la attestation
|
|
49
|
+
* (`POST /.../{id}/assertion`) queda fuera de la Entrega 1 a propósito — el contrato ya lo admite
|
|
50
|
+
* sin romperse, que es todo lo que hacía falta decidir ahora.
|
|
51
|
+
*/
|
|
52
|
+
class ChallengeDelivery {
|
|
53
|
+
}
|
|
54
|
+
exports.ChallengeDelivery = ChallengeDelivery;
|
|
55
|
+
__decorate([
|
|
56
|
+
(0, class_transformer_1.Expose)(),
|
|
57
|
+
(0, class_validator_1.IsString)(),
|
|
58
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
59
|
+
__metadata("design:type", String)
|
|
60
|
+
], ChallengeDelivery.prototype, "challenge", void 0);
|
|
61
|
+
__decorate([
|
|
62
|
+
(0, class_transformer_1.Expose)(),
|
|
63
|
+
(0, class_validator_1.IsString)(),
|
|
64
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
65
|
+
__metadata("design:type", String)
|
|
66
|
+
], ChallengeDelivery.prototype, "nonce", void 0);
|
|
67
|
+
__decorate([
|
|
68
|
+
(0, class_transformer_1.Expose)(),
|
|
69
|
+
(0, class_validator_1.IsISO8601)({ strict: true }),
|
|
70
|
+
__metadata("design:type", String)
|
|
71
|
+
], ChallengeDelivery.prototype, "expiresAt", void 0);
|
|
72
|
+
/**
|
|
73
|
+
* `deliveryMode = IMMEDIATE`. No hay nada que entregar: el biométrico ya corrió en el servidor
|
|
74
|
+
* (Amazon Rekognition) y el veredicto viene en `result` de la misma respuesta.
|
|
75
|
+
*
|
|
76
|
+
* Vacío a propósito. Existe para que la unión sea exhaustiva y el `switch` del caller no necesite
|
|
77
|
+
* un `default` que trague casos nuevos en silencio.
|
|
78
|
+
*
|
|
79
|
+
* ⚠️ Declarado, sin implementar.
|
|
80
|
+
*/
|
|
81
|
+
class ImmediateDelivery {
|
|
82
|
+
}
|
|
83
|
+
exports.ImmediateDelivery = ImmediateDelivery;
|
|
84
|
+
__decorate([
|
|
85
|
+
(0, class_transformer_1.Expose)(),
|
|
86
|
+
(0, class_validator_1.IsOptional)(),
|
|
87
|
+
(0, class_validator_1.IsString)(),
|
|
88
|
+
__metadata("design:type", String)
|
|
89
|
+
], ImmediateDelivery.prototype, "note", void 0);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Datos del SUJETO que el proveedor necesita, además del `directoryId`.
|
|
3
|
+
*
|
|
4
|
+
* 🔴 **Tipado y CERRADO, jamás un `Record<string, unknown>`.** Un mapa abierto en un boundary es
|
|
5
|
+
* antipatrón Fiado, pero el problema real es peor: sería un canal por el que cualquier caller le
|
|
6
|
+
* puede filtrar PII a un tercero sin que nadie lo revise. Lo que viaja a un proveedor externo se
|
|
7
|
+
* declara campo por campo.
|
|
8
|
+
*
|
|
9
|
+
* **No confundir con `callerContext`**, que es correlación opaca del caller y NUNCA sale de Fiado.
|
|
10
|
+
* Son dos cosas que se mezclan seguido: "datos que el proveedor necesita" vs "mi correlación".
|
|
11
|
+
*
|
|
12
|
+
* **Todos opcionales, con resolución del lado del servicio (DEC-005b).** Lo que venga acá gana; lo
|
|
13
|
+
* que falte, `biometrics-business` lo resuelve contra `fiado-identity-lambda` a partir del
|
|
14
|
+
* `directoryId`. Si tras eso sigue faltando alguno, responde `SUBJECT_DATA_INCOMPLETE` (422)
|
|
15
|
+
* nombrando los CAMPOS, jamás sus valores.
|
|
16
|
+
*
|
|
17
|
+
* 🔴 **Ese error existe para prevenir una falla muda ya vivida.** Con la metadata de identidad
|
|
18
|
+
* vacía, el proveedor genera una verificación HUÉRFANA: el usuario hace la selfie completa, el
|
|
19
|
+
* webhook no la puede asociar a nadie, y el flujo queda colgado para siempre con la persona ya
|
|
20
|
+
* fuera de la tienda. Fallar temprano y ruidoso es la única respuesta correcta.
|
|
21
|
+
*
|
|
22
|
+
* `biometrics-business` — Entrega 1.
|
|
23
|
+
*/
|
|
24
|
+
export declare class BiometricSubject {
|
|
25
|
+
/** Identificador de la persona en el directorio de Fiado. */
|
|
26
|
+
peopleId?: string;
|
|
27
|
+
/**
|
|
28
|
+
* País del sujeto en el catálogo de Fiado (`CountryId`). MX es `'484'`.
|
|
29
|
+
*
|
|
30
|
+
* ⚠️ Numérico-como-string a propósito: es el mismo valor que usa el resto de la plataforma.
|
|
31
|
+
* Un caller que mande `'MX'` acá rompería la metadata del proveedor.
|
|
32
|
+
*/
|
|
33
|
+
countryId?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Teléfono **ya en E.164**, con `+` y código de país (`+521234567890`).
|
|
36
|
+
*
|
|
37
|
+
* ⚠️ **La normalización es del caller, no de este servicio.** El wizard, por ejemplo, guarda 10
|
|
38
|
+
* dígitos crudos en su sesión y los convierte con `toE164Mx()` solo al cruzar hacia el
|
|
39
|
+
* proveedor. Mandar 10 dígitos pelados acá produce una metadata que el proveedor acepta y que
|
|
40
|
+
* después nadie puede reconciliar.
|
|
41
|
+
*/
|
|
42
|
+
phoneNumber?: string;
|
|
43
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.BiometricSubject = void 0;
|
|
13
|
+
const class_transformer_1 = require("class-transformer");
|
|
14
|
+
const class_validator_1 = require("class-validator");
|
|
15
|
+
/**
|
|
16
|
+
* Datos del SUJETO que el proveedor necesita, además del `directoryId`.
|
|
17
|
+
*
|
|
18
|
+
* 🔴 **Tipado y CERRADO, jamás un `Record<string, unknown>`.** Un mapa abierto en un boundary es
|
|
19
|
+
* antipatrón Fiado, pero el problema real es peor: sería un canal por el que cualquier caller le
|
|
20
|
+
* puede filtrar PII a un tercero sin que nadie lo revise. Lo que viaja a un proveedor externo se
|
|
21
|
+
* declara campo por campo.
|
|
22
|
+
*
|
|
23
|
+
* **No confundir con `callerContext`**, que es correlación opaca del caller y NUNCA sale de Fiado.
|
|
24
|
+
* Son dos cosas que se mezclan seguido: "datos que el proveedor necesita" vs "mi correlación".
|
|
25
|
+
*
|
|
26
|
+
* **Todos opcionales, con resolución del lado del servicio (DEC-005b).** Lo que venga acá gana; lo
|
|
27
|
+
* que falte, `biometrics-business` lo resuelve contra `fiado-identity-lambda` a partir del
|
|
28
|
+
* `directoryId`. Si tras eso sigue faltando alguno, responde `SUBJECT_DATA_INCOMPLETE` (422)
|
|
29
|
+
* nombrando los CAMPOS, jamás sus valores.
|
|
30
|
+
*
|
|
31
|
+
* 🔴 **Ese error existe para prevenir una falla muda ya vivida.** Con la metadata de identidad
|
|
32
|
+
* vacía, el proveedor genera una verificación HUÉRFANA: el usuario hace la selfie completa, el
|
|
33
|
+
* webhook no la puede asociar a nadie, y el flujo queda colgado para siempre con la persona ya
|
|
34
|
+
* fuera de la tienda. Fallar temprano y ruidoso es la única respuesta correcta.
|
|
35
|
+
*
|
|
36
|
+
* `biometrics-business` — Entrega 1.
|
|
37
|
+
*/
|
|
38
|
+
class BiometricSubject {
|
|
39
|
+
}
|
|
40
|
+
exports.BiometricSubject = BiometricSubject;
|
|
41
|
+
__decorate([
|
|
42
|
+
(0, class_transformer_1.Expose)(),
|
|
43
|
+
(0, class_validator_1.IsOptional)(),
|
|
44
|
+
(0, class_validator_1.IsString)(),
|
|
45
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
46
|
+
__metadata("design:type", String)
|
|
47
|
+
], BiometricSubject.prototype, "peopleId", void 0);
|
|
48
|
+
__decorate([
|
|
49
|
+
(0, class_transformer_1.Expose)(),
|
|
50
|
+
(0, class_validator_1.IsOptional)(),
|
|
51
|
+
(0, class_validator_1.IsString)(),
|
|
52
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
53
|
+
__metadata("design:type", String)
|
|
54
|
+
], BiometricSubject.prototype, "countryId", void 0);
|
|
55
|
+
__decorate([
|
|
56
|
+
(0, class_transformer_1.Expose)(),
|
|
57
|
+
(0, class_validator_1.IsOptional)(),
|
|
58
|
+
(0, class_validator_1.IsString)(),
|
|
59
|
+
(0, class_validator_1.Matches)(/^\+[1-9]\d{7,14}$/, {
|
|
60
|
+
message: 'phoneNumber debe venir en E.164 con + y codigo de pais (ej. +521234567890)',
|
|
61
|
+
}),
|
|
62
|
+
__metadata("design:type", String)
|
|
63
|
+
], BiometricSubject.prototype, "phoneNumber", void 0);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { BiometricProviderEnum } from '../../enums/BiometricProviderEnum';
|
|
2
|
+
import { BiometricTypeEnum } from '../../enums/BiometricTypeEnum';
|
|
3
|
+
import { BiometricSubject } from '../BiometricSubject';
|
|
4
|
+
/**
|
|
5
|
+
* `POST /private/biometric-verifications` — pedir una verificación biométrica de una persona.
|
|
6
|
+
*
|
|
7
|
+
* El caller dice QUÉ necesita (`type`), QUIÉN lo ejecuta (`provider`) y PARA QUIÉN es
|
|
8
|
+
* (`directoryId`). Todo lo demás — resolver la foto de referencia, publicarla, firmarla, armar la
|
|
9
|
+
* metadata del proveedor, acortar el link — lo hace `biometrics-business`. Ese es el punto: que el
|
|
10
|
+
* segundo producto que necesite biometría haga un `POST` en vez de un proyecto.
|
|
11
|
+
*
|
|
12
|
+
* `biometrics-business` — Entrega 1.
|
|
13
|
+
*/
|
|
14
|
+
export declare class CreateBiometricVerificationRequest {
|
|
15
|
+
/**
|
|
16
|
+
* QUÉ biométrico. Hoy solo `FACEMATCH` está implementado; el resto devuelve
|
|
17
|
+
* `BIOMETRIC_NOT_SUPPORTED` (422).
|
|
18
|
+
*/
|
|
19
|
+
type: BiometricTypeEnum;
|
|
20
|
+
/**
|
|
21
|
+
* QUIÉN lo ejecuta. **Obligatorio** (DEC-004): el caller lo nombra, el servicio no resuelve un
|
|
22
|
+
* default. Hoy solo `METAMAP`.
|
|
23
|
+
*
|
|
24
|
+
* ⚠️ **TD-001**: esto acopla a los callers. Cambiar de proveedor obliga a tocar y redesplegar a
|
|
25
|
+
* todos. Se aceptó a cambio de que la elección sea explícita y auditable en cada request.
|
|
26
|
+
*/
|
|
27
|
+
provider: BiometricProviderEnum;
|
|
28
|
+
/**
|
|
29
|
+
* PARA QUIÉN es. Es la llave con la que se resuelve la foto de referencia y los datos que falten
|
|
30
|
+
* de `subject`.
|
|
31
|
+
*
|
|
32
|
+
* `@IsNotEmpty()` y no solo `@IsString()`: un `''` colado pasaría el boundary y fallaría tres
|
|
33
|
+
* pasos río abajo, contra identity, con un error que no apunta a nada.
|
|
34
|
+
*/
|
|
35
|
+
directoryId: string;
|
|
36
|
+
/**
|
|
37
|
+
* QUIÉN pide (el producto o lambda: `surekeep-wizard`, `user-wallet-app`…). Para auditoría y
|
|
38
|
+
* trazabilidad.
|
|
39
|
+
*
|
|
40
|
+
* ⚠️ **No autoriza nada** (TD-009). El `GET` no lo valida: hoy el aislamiento es que los dos
|
|
41
|
+
* endpoints son VPC-only, así que solo lambdas de Fiado llegan. Está anotado como deuda
|
|
42
|
+
* consciente, no como olvido.
|
|
43
|
+
*/
|
|
44
|
+
requestedBy: string;
|
|
45
|
+
/**
|
|
46
|
+
* Datos del sujeto que el proveedor necesita. Lo que mandes gana; lo que falte se resuelve
|
|
47
|
+
* contra identity. Ver `BiometricSubject` — sobre todo la nota de por qué `phoneNumber` va en
|
|
48
|
+
* E.164 y la normalización es tuya.
|
|
49
|
+
*/
|
|
50
|
+
subject?: BiometricSubject;
|
|
51
|
+
/**
|
|
52
|
+
* Override de la foto de referencia. Si no viene, el servicio la resuelve solo: pide la **selfie
|
|
53
|
+
* del KYC** a identity (no el avatar `PROFILE_PICTURE`, que el KYC nunca genera) y la publica.
|
|
54
|
+
*
|
|
55
|
+
* Úsalo solo si ya tienes la foto correcta a mano y quieres ahorrarte el round-trip.
|
|
56
|
+
*/
|
|
57
|
+
referenceImageUrl?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Vigencia de la entrega, en segundos. Default 3600.
|
|
60
|
+
*
|
|
61
|
+
* El techo de 24 h no es arbitrario: lo que se entrega es una identidad abierta. Un link de
|
|
62
|
+
* verificación que sobreviva a la operación que lo pidió es una puerta sin nadie mirándola.
|
|
63
|
+
*/
|
|
64
|
+
ttlSeconds?: number;
|
|
65
|
+
/**
|
|
66
|
+
* Correlación tuya, opaca. Te la devuelvo intacta en el `GET`.
|
|
67
|
+
*
|
|
68
|
+
* 🔴 **NUNCA viaja al proveedor.** Es lo que la separa de `subject`: uno son datos que el
|
|
69
|
+
* tercero necesita, esto es un dato que solo te sirve a ti. No metas PII acá — mete tu id de
|
|
70
|
+
* sesión, de orden, de lo que sea que estés correlacionando.
|
|
71
|
+
*/
|
|
72
|
+
callerContext?: string;
|
|
73
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.CreateBiometricVerificationRequest = void 0;
|
|
13
|
+
const class_transformer_1 = require("class-transformer");
|
|
14
|
+
const class_validator_1 = require("class-validator");
|
|
15
|
+
const BiometricProviderEnum_1 = require("../../enums/BiometricProviderEnum");
|
|
16
|
+
const BiometricTypeEnum_1 = require("../../enums/BiometricTypeEnum");
|
|
17
|
+
const BiometricSubject_1 = require("../BiometricSubject");
|
|
18
|
+
/**
|
|
19
|
+
* `POST /private/biometric-verifications` — pedir una verificación biométrica de una persona.
|
|
20
|
+
*
|
|
21
|
+
* El caller dice QUÉ necesita (`type`), QUIÉN lo ejecuta (`provider`) y PARA QUIÉN es
|
|
22
|
+
* (`directoryId`). Todo lo demás — resolver la foto de referencia, publicarla, firmarla, armar la
|
|
23
|
+
* metadata del proveedor, acortar el link — lo hace `biometrics-business`. Ese es el punto: que el
|
|
24
|
+
* segundo producto que necesite biometría haga un `POST` en vez de un proyecto.
|
|
25
|
+
*
|
|
26
|
+
* `biometrics-business` — Entrega 1.
|
|
27
|
+
*/
|
|
28
|
+
class CreateBiometricVerificationRequest {
|
|
29
|
+
}
|
|
30
|
+
exports.CreateBiometricVerificationRequest = CreateBiometricVerificationRequest;
|
|
31
|
+
__decorate([
|
|
32
|
+
(0, class_transformer_1.Expose)(),
|
|
33
|
+
(0, class_validator_1.IsEnum)(BiometricTypeEnum_1.BiometricTypeEnum),
|
|
34
|
+
__metadata("design:type", String)
|
|
35
|
+
], CreateBiometricVerificationRequest.prototype, "type", void 0);
|
|
36
|
+
__decorate([
|
|
37
|
+
(0, class_transformer_1.Expose)(),
|
|
38
|
+
(0, class_validator_1.IsEnum)(BiometricProviderEnum_1.BiometricProviderEnum),
|
|
39
|
+
__metadata("design:type", String)
|
|
40
|
+
], CreateBiometricVerificationRequest.prototype, "provider", void 0);
|
|
41
|
+
__decorate([
|
|
42
|
+
(0, class_transformer_1.Expose)(),
|
|
43
|
+
(0, class_validator_1.IsString)(),
|
|
44
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
45
|
+
__metadata("design:type", String)
|
|
46
|
+
], CreateBiometricVerificationRequest.prototype, "directoryId", void 0);
|
|
47
|
+
__decorate([
|
|
48
|
+
(0, class_transformer_1.Expose)(),
|
|
49
|
+
(0, class_validator_1.IsString)(),
|
|
50
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
51
|
+
__metadata("design:type", String)
|
|
52
|
+
], CreateBiometricVerificationRequest.prototype, "requestedBy", void 0);
|
|
53
|
+
__decorate([
|
|
54
|
+
(0, class_transformer_1.Expose)(),
|
|
55
|
+
(0, class_validator_1.IsOptional)(),
|
|
56
|
+
(0, class_validator_1.ValidateNested)(),
|
|
57
|
+
(0, class_transformer_1.Type)(() => BiometricSubject_1.BiometricSubject),
|
|
58
|
+
__metadata("design:type", BiometricSubject_1.BiometricSubject)
|
|
59
|
+
], CreateBiometricVerificationRequest.prototype, "subject", void 0);
|
|
60
|
+
__decorate([
|
|
61
|
+
(0, class_transformer_1.Expose)(),
|
|
62
|
+
(0, class_validator_1.IsOptional)(),
|
|
63
|
+
(0, class_validator_1.IsString)(),
|
|
64
|
+
(0, class_validator_1.IsNotEmpty)(),
|
|
65
|
+
__metadata("design:type", String)
|
|
66
|
+
], CreateBiometricVerificationRequest.prototype, "referenceImageUrl", void 0);
|
|
67
|
+
__decorate([
|
|
68
|
+
(0, class_transformer_1.Expose)(),
|
|
69
|
+
(0, class_validator_1.IsOptional)(),
|
|
70
|
+
(0, class_validator_1.IsInt)(),
|
|
71
|
+
(0, class_validator_1.Min)(60),
|
|
72
|
+
(0, class_validator_1.Max)(86400),
|
|
73
|
+
__metadata("design:type", Number)
|
|
74
|
+
], CreateBiometricVerificationRequest.prototype, "ttlSeconds", void 0);
|
|
75
|
+
__decorate([
|
|
76
|
+
(0, class_transformer_1.Expose)(),
|
|
77
|
+
(0, class_validator_1.IsOptional)(),
|
|
78
|
+
(0, class_validator_1.IsString)(),
|
|
79
|
+
(0, class_validator_1.MaxLength)(256),
|
|
80
|
+
__metadata("design:type", String)
|
|
81
|
+
], CreateBiometricVerificationRequest.prototype, "callerContext", void 0);
|