@fiado/type-kit 3.147.0 → 3.149.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/platformRbac/auth/PrepareChallengeRequest.test.ts +64 -0
- package/bin/platformRbac/application/cascade.d.ts +45 -0
- package/bin/platformRbac/application/cascade.js +2 -0
- package/bin/platformRbac/auth/PrepareChallengeRequest.d.ts +1 -1
- package/bin/platformRbac/auth/PrepareChallengeRequest.js +1 -0
- package/bin/platformRbac/index.d.ts +1 -0
- package/package.json +1 -1
- package/src/platformRbac/application/cascade.ts +51 -0
- package/src/platformRbac/auth/PrepareChallengeRequest.ts +8 -2
- package/src/platformRbac/index.ts +7 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { plainToInstance } from 'class-transformer';
|
|
3
|
+
import { validate } from 'class-validator';
|
|
4
|
+
import { PrepareChallengeRequest } from '../../../../src/platformRbac/auth/PrepareChallengeRequest';
|
|
5
|
+
import { ChallengeNameEnum } from '../../../../src/platformRbac/enums/ChallengeNameEnum';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* DEC-RBAC-094: `email` es opcional y robusto a string vacío. Un usuario phone-primario
|
|
9
|
+
* (sin email) hace login MFA (SMS_OTP / TOTP) sin email — el trigger CreateAuthChallenge
|
|
10
|
+
* ya no manda `''`. `@ValidateIf` saltea undefined/null/'' (`@IsOptional` NO saltea `''`).
|
|
11
|
+
* La rama EMAIL_OTP del preparer sigue necesitando email en runtime — eso NO lo valida el DTO.
|
|
12
|
+
*/
|
|
13
|
+
describe('PrepareChallengeRequest', () => {
|
|
14
|
+
const base = {
|
|
15
|
+
cognitoSub: 'f4586478-20c1-700c-c40a-2396db1affe3',
|
|
16
|
+
challengeName: ChallengeNameEnum.SMS_OTP,
|
|
17
|
+
phone: '+524422194128',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
it('valida SMS_OTP sin email (undefined)', async () => {
|
|
21
|
+
const dto = plainToInstance(PrepareChallengeRequest, { ...base });
|
|
22
|
+
const errors = await validate(dto);
|
|
23
|
+
expect(errors).toEqual([]);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('valida SMS_OTP con email = "" (string vacío del trigger legacy)', async () => {
|
|
27
|
+
const dto = plainToInstance(PrepareChallengeRequest, { ...base, email: '' });
|
|
28
|
+
const errors = await validate(dto);
|
|
29
|
+
expect(errors).toEqual([]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('valida SMS_OTP con email = null', async () => {
|
|
33
|
+
const dto = plainToInstance(PrepareChallengeRequest, { ...base, email: null });
|
|
34
|
+
const errors = await validate(dto);
|
|
35
|
+
expect(errors).toEqual([]);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('valida cuando email SÍ viene y es un email válido', async () => {
|
|
39
|
+
const dto = plainToInstance(PrepareChallengeRequest, {
|
|
40
|
+
...base,
|
|
41
|
+
challengeName: ChallengeNameEnum.EMAIL_OTP,
|
|
42
|
+
email: 'user@dominio.com',
|
|
43
|
+
});
|
|
44
|
+
const errors = await validate(dto);
|
|
45
|
+
expect(errors).toEqual([]);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('falla cuando email viene pero NO es un email válido', async () => {
|
|
49
|
+
const dto = plainToInstance(PrepareChallengeRequest, { ...base, email: 'no-es-email' });
|
|
50
|
+
const errors = await validate(dto);
|
|
51
|
+
expect(errors.length).toBeGreaterThan(0);
|
|
52
|
+
expect(errors[0].property).toBe('email');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('falla si challengeName no es un valor del enum', async () => {
|
|
56
|
+
const dto = plainToInstance(PrepareChallengeRequest, {
|
|
57
|
+
...base,
|
|
58
|
+
challengeName: 'BOGUS' as ChallengeNameEnum,
|
|
59
|
+
});
|
|
60
|
+
const errors = await validate(dto);
|
|
61
|
+
expect(errors.length).toBeGreaterThan(0);
|
|
62
|
+
expect(errors[0].property).toBe('challengeName');
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { PermissionScope } from '../enums/PermissionScope';
|
|
2
|
+
import { ApplicationPermission } from './ApplicationPermission';
|
|
3
|
+
/**
|
|
4
|
+
* DEC-RBAC-092: al actualizar el `scope` de un permiso del catálogo de una app, el backend recalcula
|
|
5
|
+
* en cascada el scope de los roles que lo usan en todos los tenants de esa app y REPORTA (no modifica)
|
|
6
|
+
* las asignaciones que quedaron inconsistentes (Opción A). Estos tipos son el contrato de ese reporte.
|
|
7
|
+
*/
|
|
8
|
+
/** Un rol cuyo scope se recalculó (y cambió) por el cascade. */
|
|
9
|
+
export interface RoleScopeRecompute {
|
|
10
|
+
tenantId: string;
|
|
11
|
+
roleId: string;
|
|
12
|
+
previousScope: PermissionScope;
|
|
13
|
+
newScope: PermissionScope;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Asignación que quedó inconsistente: su `scope` almacenado ya no coincide con el nuevo scope del rol.
|
|
17
|
+
* NO se modifica; se reporta para que el admin reasigne (DEC-RBAC-092, Opción A).
|
|
18
|
+
*/
|
|
19
|
+
export interface InconsistentAssignment {
|
|
20
|
+
tenantId: string;
|
|
21
|
+
roleId: string;
|
|
22
|
+
assignmentId: string;
|
|
23
|
+
cognitoSub: string;
|
|
24
|
+
assignmentScope: PermissionScope;
|
|
25
|
+
assignmentScopeRef: string;
|
|
26
|
+
}
|
|
27
|
+
/** Un silo (tenant) cuyo recálculo falló (best-effort — no aborta el resto). */
|
|
28
|
+
export interface CascadeTenantFailure {
|
|
29
|
+
tenantId: string;
|
|
30
|
+
error: string;
|
|
31
|
+
}
|
|
32
|
+
/** Resumen del cascade de recálculo de scope de roles (DEC-RBAC-092). */
|
|
33
|
+
export interface CascadeReport {
|
|
34
|
+
rolesRecomputed: RoleScopeRecompute[];
|
|
35
|
+
inconsistentAssignments: InconsistentAssignment[];
|
|
36
|
+
failedTenants: CascadeTenantFailure[];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Respuesta de `PUT /applications/{id}/permissions/{key}`: el permiso actualizado + (si el cambio de
|
|
40
|
+
* scope disparó el cascade) el resumen. `cascade` ausente si el update no cambió el scope.
|
|
41
|
+
*/
|
|
42
|
+
export interface UpdateApplicationPermissionResponse {
|
|
43
|
+
permission: ApplicationPermission;
|
|
44
|
+
cascade?: CascadeReport;
|
|
45
|
+
}
|
|
@@ -24,6 +24,7 @@ __decorate([
|
|
|
24
24
|
__metadata("design:type", String)
|
|
25
25
|
], PrepareChallengeRequest.prototype, "challengeName", void 0);
|
|
26
26
|
__decorate([
|
|
27
|
+
(0, class_validator_1.ValidateIf)((o) => o.email !== undefined && o.email !== null && o.email !== ''),
|
|
27
28
|
(0, class_validator_1.IsEmail)(),
|
|
28
29
|
__metadata("design:type", String)
|
|
29
30
|
], PrepareChallengeRequest.prototype, "email", void 0);
|
|
@@ -44,6 +44,7 @@ export * from './enums/PermissionKind';
|
|
|
44
44
|
export type { LevelDef, ApplicationSecurityPolicyDefault, ApplicationBrandingDefault, AutoregisterAllowedRole, AutoregisterConfig, ApplicationDefaults, SeedRole, Application, } from './application/Application';
|
|
45
45
|
export type { ApplicationPermission } from './application/ApplicationPermission';
|
|
46
46
|
export type { CreateApplicationRequest, UpdateApplicationRequest, UpsertApplicationPermissionRequest, } from './application/requests';
|
|
47
|
+
export type { RoleScopeRecompute, InconsistentAssignment, CascadeTenantFailure, CascadeReport, UpdateApplicationPermissionResponse, } from './application/cascade';
|
|
47
48
|
export * from './dtos/SelfRegisterStartRequest';
|
|
48
49
|
export * from './dtos/SelfRegisterVerifyOtpRequest';
|
|
49
50
|
export * from './dtos/SelfRegisterCompleteRequest';
|
package/package.json
CHANGED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { PermissionScope } from '../enums/PermissionScope';
|
|
2
|
+
import { ApplicationPermission } from './ApplicationPermission';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* DEC-RBAC-092: al actualizar el `scope` de un permiso del catálogo de una app, el backend recalcula
|
|
6
|
+
* en cascada el scope de los roles que lo usan en todos los tenants de esa app y REPORTA (no modifica)
|
|
7
|
+
* las asignaciones que quedaron inconsistentes (Opción A). Estos tipos son el contrato de ese reporte.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Un rol cuyo scope se recalculó (y cambió) por el cascade. */
|
|
11
|
+
export interface RoleScopeRecompute {
|
|
12
|
+
tenantId: string;
|
|
13
|
+
roleId: string;
|
|
14
|
+
previousScope: PermissionScope;
|
|
15
|
+
newScope: PermissionScope;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Asignación que quedó inconsistente: su `scope` almacenado ya no coincide con el nuevo scope del rol.
|
|
20
|
+
* NO se modifica; se reporta para que el admin reasigne (DEC-RBAC-092, Opción A).
|
|
21
|
+
*/
|
|
22
|
+
export interface InconsistentAssignment {
|
|
23
|
+
tenantId: string;
|
|
24
|
+
roleId: string;
|
|
25
|
+
assignmentId: string;
|
|
26
|
+
cognitoSub: string;
|
|
27
|
+
assignmentScope: PermissionScope;
|
|
28
|
+
assignmentScopeRef: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Un silo (tenant) cuyo recálculo falló (best-effort — no aborta el resto). */
|
|
32
|
+
export interface CascadeTenantFailure {
|
|
33
|
+
tenantId: string;
|
|
34
|
+
error: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Resumen del cascade de recálculo de scope de roles (DEC-RBAC-092). */
|
|
38
|
+
export interface CascadeReport {
|
|
39
|
+
rolesRecomputed: RoleScopeRecompute[];
|
|
40
|
+
inconsistentAssignments: InconsistentAssignment[];
|
|
41
|
+
failedTenants: CascadeTenantFailure[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Respuesta de `PUT /applications/{id}/permissions/{key}`: el permiso actualizado + (si el cambio de
|
|
46
|
+
* scope disparó el cascade) el resumen. `cascade` ausente si el update no cambió el scope.
|
|
47
|
+
*/
|
|
48
|
+
export interface UpdateApplicationPermissionResponse {
|
|
49
|
+
permission: ApplicationPermission;
|
|
50
|
+
cascade?: CascadeReport;
|
|
51
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IsEmail, IsEnum, IsOptional, IsString } from 'class-validator';
|
|
1
|
+
import { IsEmail, IsEnum, IsOptional, IsString, ValidateIf } from 'class-validator';
|
|
2
2
|
import { ChallengeNameEnum } from '../enums/ChallengeNameEnum';
|
|
3
3
|
|
|
4
4
|
export class PrepareChallengeRequest {
|
|
@@ -8,8 +8,14 @@ export class PrepareChallengeRequest {
|
|
|
8
8
|
@IsEnum(ChallengeNameEnum)
|
|
9
9
|
challengeName!: ChallengeNameEnum;
|
|
10
10
|
|
|
11
|
+
// DEC-RBAC-094: email OPCIONAL y robusto a string vacío. Un usuario phone-primario (sin email)
|
|
12
|
+
// hace login MFA (SMS_OTP/TOTP) sin email; el trigger CreateAuthChallenge ya no manda ''. Se usa
|
|
13
|
+
// @ValidateIf (no @IsOptional) porque @IsOptional NO saltea '' — acá necesitamos que undefined,
|
|
14
|
+
// null y '' NO rompan la validación. Cuando email SÍ trae valor no vacío, debe ser un email válido.
|
|
15
|
+
// La rama EMAIL_OTP del preparer sigue exigiendo email en runtime (no se fuerza en el DTO). Ver DECISIONES.md.
|
|
16
|
+
@ValidateIf((o) => o.email !== undefined && o.email !== null && o.email !== '')
|
|
11
17
|
@IsEmail()
|
|
12
|
-
email
|
|
18
|
+
email?: string;
|
|
13
19
|
|
|
14
20
|
// DEC-RBAC-083: teléfono destino del challenge SMS_OTP. Lo popula el trigger CreateAuthChallenge
|
|
15
21
|
// (JWTAuth-cognito) desde el atributo phone_number de Cognito, igual que email. Opcional: ausente
|
|
@@ -106,6 +106,13 @@ export type {
|
|
|
106
106
|
UpdateApplicationRequest,
|
|
107
107
|
UpsertApplicationPermissionRequest,
|
|
108
108
|
} from './application/requests';
|
|
109
|
+
export type {
|
|
110
|
+
RoleScopeRecompute,
|
|
111
|
+
InconsistentAssignment,
|
|
112
|
+
CascadeTenantFailure,
|
|
113
|
+
CascadeReport,
|
|
114
|
+
UpdateApplicationPermissionResponse,
|
|
115
|
+
} from './application/cascade';
|
|
109
116
|
|
|
110
117
|
// Autoregistro self-service por tenant (DEC-RBAC-030..034). Los Request llevan decoradores
|
|
111
118
|
// class-validator (export de valor — se hidratan con plainToInstance); los Response son interfaces
|