@uniorganization/uni-lib 4.1.4 → 4.3.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.
@@ -62,7 +62,6 @@ let JwtStrategy = class JwtStrategy extends (0, passport_1.PassportStrategy)(pas
62
62
  const impersonating = originalClientId !== null;
63
63
  if ((impersonating &&
64
64
  (originalClientId !== tenant_1.NEWTECH_CLIENT_ID ||
65
- homeClientId !== tenant_1.NEWTECH_CLIENT_ID ||
66
65
  effectiveClientId === tenant_1.NEWTECH_CLIENT_ID ||
67
66
  !((_b = signedUser.roles) === null || _b === void 0 ? void 0 : _b.includes(tenant_1.TENANT_IMPERSONATOR_ROLE)) ||
68
67
  !signedUser.impersonatedAt ||
@@ -71,13 +70,14 @@ let JwtStrategy = class JwtStrategy extends (0, passport_1.PassportStrategy)(pas
71
70
  throw new common_1.UnauthorizedException();
72
71
  }
73
72
  const resolved = impersonating
74
- ? yield this.authorization.resolveImpersonation(signedUser.id, homeClientId, effectiveClientId)
73
+ ? yield this.authorization.resolveImpersonation(signedUser.id, originalClientId, homeClientId, effectiveClientId)
75
74
  : yield this.authorization.resolve(signedUser.id, effectiveClientId, homeClientId);
76
75
  const { identity, snapshot } = resolved;
77
76
  const isMasterTenantAdmin = !impersonating &&
78
77
  snapshot.clientId === tenant_1.NEWTECH_CLIENT_ID &&
79
- snapshot.roles.some((role) => role.moduleName.toLowerCase() === 'root' &&
80
- role.roleName.toLowerCase() === 'sysadmin');
78
+ (snapshot.permissions.includes('master.users.read') ||
79
+ snapshot.roles.some((role) => role.moduleName.toLowerCase() === 'root' &&
80
+ role.roleName.toLowerCase() === 'sysadmin'));
81
81
  return {
82
82
  id: identity.id,
83
83
  first_name: identity.firstName,
@@ -104,6 +104,9 @@ let JwtStrategy = class JwtStrategy extends (0, passport_1.PassportStrategy)(pas
104
104
  : null,
105
105
  roles: Array.from(new Set(snapshot.roles.map((role) => role.roleName))),
106
106
  privileges: snapshot.privileges,
107
+ role: snapshot.accessRole,
108
+ permissions: snapshot.permissions,
109
+ authzVersion: snapshot.authzVersion,
107
110
  isMasterTenantAdmin,
108
111
  };
109
112
  });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const common_1 = require("@nestjs/common");
13
+ const tenant_1 = require("../tenant");
14
+ const jwt_strategy_1 = require("./jwt.strategy");
15
+ describe('JwtStrategy impersonated sessions', () => {
16
+ process.env.JWT_SECRET = 'test-secret';
17
+ const authorization = { resolveImpersonation: jest.fn() };
18
+ const securityState = { isAccessTokenRevoked: jest.fn() };
19
+ const strategy = new jwt_strategy_1.JwtStrategy(authorization, securityState);
20
+ const payload = (overrides = {}) => {
21
+ const now = Math.floor(Date.now() / 1000);
22
+ const expiresAt = now + 900;
23
+ const user = Object.assign({ id: 1, homeClientId: 2, clientId: 2, clientName: 'Altera', clientAlias: 'altera', jti: 'impersonated-jti', tokenExpiresAt: expiresAt, originalClientId: tenant_1.NEWTECH_CLIENT_ID, impersonatedAt: new Date().toISOString(), impersonationReason: 'Investigate issue', roles: [tenant_1.TENANT_IMPERSONATOR_ROLE], privileges: [], role: {
24
+ id: 0,
25
+ code: tenant_1.TENANT_IMPERSONATOR_ROLE,
26
+ name: 'Tenant impersonator',
27
+ scope: 'GLOBAL',
28
+ clientId: null,
29
+ }, permissions: [], authzVersion: 1, isMasterTenantAdmin: false }, overrides);
30
+ return {
31
+ type: 'access',
32
+ user,
33
+ sub: '1',
34
+ iat: now - 10,
35
+ exp: expiresAt,
36
+ jti: 'impersonated-jti',
37
+ };
38
+ };
39
+ beforeEach(() => {
40
+ jest.clearAllMocks();
41
+ securityState.isAccessTokenRevoked.mockResolvedValue(false);
42
+ authorization.resolveImpersonation.mockResolvedValue({
43
+ identity: { id: 1, homeClientId: 2 },
44
+ snapshot: {
45
+ userId: 1,
46
+ homeClientId: 2,
47
+ clientId: 2,
48
+ clientName: 'Altera',
49
+ clientAlias: 'altera',
50
+ roles: [
51
+ {
52
+ roleId: 0,
53
+ roleName: tenant_1.TENANT_IMPERSONATOR_ROLE,
54
+ moduleId: 0,
55
+ moduleName: tenant_1.TENANT_IMPERSONATOR_ROLE,
56
+ },
57
+ ],
58
+ privileges: [],
59
+ accessRole: {
60
+ id: 0,
61
+ code: tenant_1.TENANT_IMPERSONATOR_ROLE,
62
+ name: 'Tenant impersonator',
63
+ scope: 'GLOBAL',
64
+ clientId: null,
65
+ },
66
+ permissions: [],
67
+ authzVersion: 1,
68
+ identity: { id: 1, homeClientId: 2 },
69
+ cachedAt: new Date().toISOString(),
70
+ },
71
+ });
72
+ });
73
+ it('revalidates an impersonated token with an Altera home client through Newtech', () => __awaiter(void 0, void 0, void 0, function* () {
74
+ const validated = yield strategy.validate(payload());
75
+ expect(authorization.resolveImpersonation).toHaveBeenCalledWith(1, 1, 2, 2);
76
+ expect(validated.homeClientId).toBe(2);
77
+ expect(validated.clientId).toBe(2);
78
+ expect(validated.originalClientId).toBe(tenant_1.NEWTECH_CLIENT_ID);
79
+ }));
80
+ const invalidTokenCases = [
81
+ [{ originalClientId: 2 }, 'a non-Newtech origin'],
82
+ [{ roles: [] }, 'a token without the impersonator role'],
83
+ [{ impersonationReason: null }, 'a token without a support reason'],
84
+ ];
85
+ it.each(invalidTokenCases)('rejects %s', (overrides) => __awaiter(void 0, void 0, void 0, function* () {
86
+ yield expect(strategy.validate(payload(overrides))).rejects.toBeInstanceOf(common_1.UnauthorizedException);
87
+ expect(authorization.resolveImpersonation).not.toHaveBeenCalled();
88
+ }));
89
+ });
@@ -154,6 +154,16 @@ let RedisTenantStateStore = RedisTenantStateStore_1 = class RedisTenantStateStor
154
154
  typeof role.moduleName === 'string') &&
155
155
  Array.isArray(value.privileges) &&
156
156
  value.privileges.every((privilege) => typeof privilege === 'string') &&
157
+ value.accessRole &&
158
+ Number.isInteger(value.accessRole.id) &&
159
+ value.accessRole.id >= 0 &&
160
+ typeof value.accessRole.code === 'string' &&
161
+ typeof value.accessRole.name === 'string' &&
162
+ ['GLOBAL', 'CLIENT'].includes(value.accessRole.scope) &&
163
+ Array.isArray(value.permissions) &&
164
+ value.permissions.every((permission) => typeof permission === 'string') &&
165
+ Number.isInteger(value.authzVersion) &&
166
+ value.authzVersion > 0 &&
157
167
  ((_a = value.identity) === null || _a === void 0 ? void 0 : _a.id) === value.userId &&
158
168
  value.identity.homeClientId === value.homeClientId &&
159
169
  typeof value.cachedAt === 'string');
@@ -13,7 +13,7 @@ export declare class TenantAuthorizationService {
13
13
  private readonly logger;
14
14
  constructor(store: TenantAuthorizationStore, userRepository: Repository<Users>, clientRepository: Repository<Client>, userRoleRepository: Repository<UserRoles>);
15
15
  resolve(userId: number, clientId: number, expectedHomeClientId: number): Promise<ResolvedTenantAuthorization>;
16
- resolveImpersonation(userId: number, homeClientId: number, targetClientId: number): Promise<ResolvedTenantAuthorization>;
16
+ resolveImpersonation(userId: number, originClientId: number, homeClientId: number, targetClientId: number): Promise<ResolvedTenantAuthorization>;
17
17
  invalidate(userId: number, clientId: number): Promise<void>;
18
18
  invalidateMany(clientId: number, userIds: readonly number[]): Promise<void>;
19
19
  private loadFromDatabase;
@@ -64,15 +64,16 @@ let TenantAuthorizationService = TenantAuthorizationService_1 = class TenantAuth
64
64
  return resolved;
65
65
  });
66
66
  }
67
- resolveImpersonation(userId, homeClientId, targetClientId) {
67
+ resolveImpersonation(userId, originClientId, homeClientId, targetClientId) {
68
68
  return __awaiter(this, void 0, void 0, function* () {
69
- if (homeClientId !== tenant_constants_1.NEWTECH_CLIENT_ID ||
69
+ if (originClientId !== tenant_constants_1.NEWTECH_CLIENT_ID ||
70
70
  targetClientId === tenant_constants_1.NEWTECH_CLIENT_ID) {
71
71
  throw new common_1.UnauthorizedException();
72
72
  }
73
- const origin = yield this.resolve(userId, homeClientId, homeClientId);
74
- const isNewtechMaster = origin.snapshot.roles.some((role) => role.moduleName.toLowerCase() === 'root' &&
75
- role.roleName.toLowerCase() === 'sysadmin');
73
+ const origin = yield this.resolve(userId, originClientId, homeClientId);
74
+ const isNewtechMaster = origin.snapshot.permissions.includes('master.users.read') ||
75
+ origin.snapshot.roles.some((role) => role.moduleName.toLowerCase() === 'root' &&
76
+ role.roleName.toLowerCase() === 'sysadmin');
76
77
  const targetClient = yield this.clientRepository.findOne({
77
78
  where: { id: targetClientId, deletedAt: (0, typeorm_2.IsNull)() },
78
79
  });
@@ -94,6 +95,15 @@ let TenantAuthorizationService = TenantAuthorizationService_1 = class TenantAuth
94
95
  },
95
96
  ],
96
97
  privileges: [],
98
+ accessRole: {
99
+ id: 0,
100
+ code: tenant_constants_1.TENANT_IMPERSONATOR_ROLE,
101
+ name: 'Tenant impersonator',
102
+ scope: 'GLOBAL',
103
+ clientId: null,
104
+ },
105
+ permissions: [],
106
+ authzVersion: 1,
97
107
  identity: origin.identity,
98
108
  cachedAt: new Date().toISOString(),
99
109
  };
@@ -115,7 +125,7 @@ let TenantAuthorizationService = TenantAuthorizationService_1 = class TenantAuth
115
125
  }
116
126
  loadFromDatabase(userId, clientId, expectedHomeClientId) {
117
127
  return __awaiter(this, void 0, void 0, function* () {
118
- const [user, client, assignments] = yield Promise.all([
128
+ const [user, client, assignments, accessRows] = yield Promise.all([
119
129
  this.userRepository.findOne({ where: { id: userId } }),
120
130
  this.clientRepository.findOne({
121
131
  where: { id: clientId, deletedAt: (0, typeorm_2.IsNull)() },
@@ -129,23 +139,64 @@ let TenantAuthorizationService = TenantAuthorizationService_1 = class TenantAuth
129
139
  .where('assignment."USER_ID" = :userId', { userId })
130
140
  .andWhere('assignment.client_id = :clientId', { clientId })
131
141
  .getMany(),
142
+ this.userRoleRepository.manager.query(`SELECT
143
+ role.id,
144
+ role.code,
145
+ role.name,
146
+ role.scope,
147
+ role.client_id AS "roleClientId",
148
+ role.authz_version AS "authzVersion",
149
+ COALESCE(
150
+ jsonb_agg(permission.code ORDER BY permission.code)
151
+ FILTER (WHERE permission.id IS NOT NULL AND permission.is_active),
152
+ '[]'::jsonb
153
+ ) AS permissions
154
+ FROM stm."USER_ACCESS_ROLES" membership
155
+ JOIN stm."ACCESS_ROLES" role
156
+ ON role.id = membership.role_id
157
+ AND role.is_active
158
+ LEFT JOIN stm."ACCESS_ROLE_PERMISSIONS" definition
159
+ ON definition.role_id = role.id
160
+ LEFT JOIN stm."ACCESS_PERMISSIONS" permission
161
+ ON permission.id = definition.permission_id
162
+ WHERE membership.user_id = $1
163
+ AND membership.client_id = $2
164
+ GROUP BY role.id`, [userId, clientId]),
132
165
  ]);
133
166
  if (!user ||
134
167
  !Number(user.is_active) ||
135
168
  user.clientId !== expectedHomeClientId ||
136
169
  !client ||
137
- assignments.length === 0) {
170
+ accessRows.length !== 1) {
138
171
  throw new common_1.UnauthorizedException();
139
172
  }
140
- const roles = assignments.map((assignment) => {
141
- var _a, _b, _c, _d, _e, _f;
142
- return ({
143
- roleId: Number(assignment.roleId || ((_a = assignment.role) === null || _a === void 0 ? void 0 : _a.id)),
144
- roleName: (_b = assignment.role) === null || _b === void 0 ? void 0 : _b.roleName,
145
- moduleId: Number((_d = (_c = assignment.role) === null || _c === void 0 ? void 0 : _c.module) === null || _d === void 0 ? void 0 : _d.id),
146
- moduleName: (_f = (_e = assignment.role) === null || _e === void 0 ? void 0 : _e.module) === null || _f === void 0 ? void 0 : _f.moduleName,
147
- });
148
- });
173
+ const accessRow = accessRows[0];
174
+ const accessRole = {
175
+ id: Number(accessRow.id),
176
+ code: accessRow.code,
177
+ name: accessRow.name,
178
+ scope: accessRow.scope,
179
+ clientId: accessRow.roleClientId == null ? null : Number(accessRow.roleClientId),
180
+ };
181
+ const usingLegacyCompatibility = accessRole.code.startsWith('migrated-');
182
+ const roles = usingLegacyCompatibility && assignments.length
183
+ ? assignments.map((assignment) => {
184
+ var _a, _b, _c, _d, _e, _f;
185
+ return ({
186
+ roleId: Number(assignment.roleId || ((_a = assignment.role) === null || _a === void 0 ? void 0 : _a.id)),
187
+ roleName: (_b = assignment.role) === null || _b === void 0 ? void 0 : _b.roleName,
188
+ moduleId: Number((_d = (_c = assignment.role) === null || _c === void 0 ? void 0 : _c.module) === null || _d === void 0 ? void 0 : _d.id),
189
+ moduleName: (_f = (_e = assignment.role) === null || _e === void 0 ? void 0 : _e.module) === null || _f === void 0 ? void 0 : _f.moduleName,
190
+ });
191
+ })
192
+ : [
193
+ {
194
+ roleId: accessRole.id,
195
+ roleName: accessRole.name,
196
+ moduleId: accessRole.id,
197
+ moduleName: 'access-control',
198
+ },
199
+ ];
149
200
  if (roles.some((role) => !Number.isInteger(role.roleId) ||
150
201
  role.roleId <= 0 ||
151
202
  !role.roleName ||
@@ -154,12 +205,14 @@ let TenantAuthorizationService = TenantAuthorizationService_1 = class TenantAuth
154
205
  !role.moduleName)) {
155
206
  throw new common_1.UnauthorizedException();
156
207
  }
157
- const privileges = Array.from(new Set(assignments.flatMap((assignment) => {
158
- var _a;
159
- return (((_a = assignment.role) === null || _a === void 0 ? void 0 : _a.privileges) || [])
160
- .map((definition) => { var _a; return (_a = definition.privilege) === null || _a === void 0 ? void 0 : _a.privilege; })
161
- .filter((privilege) => Boolean(privilege));
162
- }))).sort();
208
+ const privileges = usingLegacyCompatibility
209
+ ? Array.from(new Set(assignments.flatMap((assignment) => {
210
+ var _a;
211
+ return (((_a = assignment.role) === null || _a === void 0 ? void 0 : _a.privileges) || [])
212
+ .map((definition) => { var _a; return (_a = definition.privilege) === null || _a === void 0 ? void 0 : _a.privilege; })
213
+ .filter((privilege) => Boolean(privilege));
214
+ }))).sort()
215
+ : [];
163
216
  const identity = {
164
217
  id: user.id,
165
218
  homeClientId: user.clientId,
@@ -183,6 +236,11 @@ let TenantAuthorizationService = TenantAuthorizationService_1 = class TenantAuth
183
236
  clientAlias: client.alias,
184
237
  roles,
185
238
  privileges,
239
+ accessRole,
240
+ permissions: Array.isArray(accessRow.permissions)
241
+ ? accessRow.permissions
242
+ : [],
243
+ authzVersion: Number(accessRow.authzVersion),
186
244
  identity,
187
245
  cachedAt: new Date().toISOString(),
188
246
  };
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const common_1 = require("@nestjs/common");
13
+ const tenant_constants_1 = require("./tenant.constants");
14
+ const tenant_authorization_service_1 = require("./tenant-authorization.service");
15
+ describe('TenantAuthorizationService impersonation', () => {
16
+ const store = {
17
+ get: jest.fn(),
18
+ set: jest.fn(),
19
+ delete: jest.fn(),
20
+ deleteMany: jest.fn(),
21
+ };
22
+ const userRepository = {};
23
+ const clientRepository = { findOne: jest.fn() };
24
+ const userRoleRepository = {};
25
+ const service = new tenant_authorization_service_1.TenantAuthorizationService(store, userRepository, clientRepository, userRoleRepository);
26
+ const origin = (permissions) => ({
27
+ identity: {
28
+ id: 1,
29
+ homeClientId: 2,
30
+ firstName: 'Root',
31
+ lastName: 'User',
32
+ },
33
+ snapshot: {
34
+ userId: 1,
35
+ homeClientId: 2,
36
+ clientId: tenant_constants_1.NEWTECH_CLIENT_ID,
37
+ clientName: 'Newtech',
38
+ clientAlias: 'newtech',
39
+ roles: [
40
+ {
41
+ roleId: 8,
42
+ roleName: 'Master Root',
43
+ moduleId: 8,
44
+ moduleName: 'access-control',
45
+ },
46
+ ],
47
+ privileges: [],
48
+ accessRole: {
49
+ id: 8,
50
+ code: 'master-root',
51
+ name: 'Master Root',
52
+ scope: 'CLIENT',
53
+ clientId: tenant_constants_1.NEWTECH_CLIENT_ID,
54
+ },
55
+ permissions,
56
+ authzVersion: 3,
57
+ identity: {
58
+ id: 1,
59
+ homeClientId: 2,
60
+ firstName: 'Root',
61
+ lastName: 'User',
62
+ },
63
+ cachedAt: new Date().toISOString(),
64
+ },
65
+ });
66
+ beforeEach(() => {
67
+ jest.clearAllMocks();
68
+ clientRepository.findOne.mockResolvedValue({
69
+ id: 2,
70
+ name: 'Altera',
71
+ alias: 'altera',
72
+ });
73
+ });
74
+ it('resolves support from active Newtech when the permanent home is Altera', () => __awaiter(void 0, void 0, void 0, function* () {
75
+ jest
76
+ .spyOn(service, 'resolve')
77
+ .mockResolvedValue(origin(['master.users.read']));
78
+ const resolved = yield service.resolveImpersonation(1, tenant_constants_1.NEWTECH_CLIENT_ID, 2, 2);
79
+ expect(service.resolve).toHaveBeenCalledWith(1, tenant_constants_1.NEWTECH_CLIENT_ID, 2);
80
+ expect(resolved.snapshot.clientId).toBe(2);
81
+ expect(resolved.snapshot.homeClientId).toBe(2);
82
+ expect(resolved.snapshot.clientName).toBe('Altera');
83
+ }));
84
+ it('rejects an origin outside Newtech', () => __awaiter(void 0, void 0, void 0, function* () {
85
+ yield expect(service.resolveImpersonation(1, 2, 2, 2)).rejects.toBeInstanceOf(common_1.UnauthorizedException);
86
+ }));
87
+ it('rejects Newtech as the target', () => __awaiter(void 0, void 0, void 0, function* () {
88
+ yield expect(service.resolveImpersonation(1, tenant_constants_1.NEWTECH_CLIENT_ID, 2, tenant_constants_1.NEWTECH_CLIENT_ID)).rejects.toBeInstanceOf(common_1.UnauthorizedException);
89
+ }));
90
+ it('rejects an origin without master access', () => __awaiter(void 0, void 0, void 0, function* () {
91
+ jest.spyOn(service, 'resolve').mockResolvedValue(origin([]));
92
+ yield expect(service.resolveImpersonation(1, tenant_constants_1.NEWTECH_CLIENT_ID, 2, 2)).rejects.toBeInstanceOf(common_1.UnauthorizedException);
93
+ }));
94
+ it('rejects an inactive or missing target client', () => __awaiter(void 0, void 0, void 0, function* () {
95
+ jest
96
+ .spyOn(service, 'resolve')
97
+ .mockResolvedValue(origin(['master.users.read']));
98
+ clientRepository.findOne.mockResolvedValue(null);
99
+ yield expect(service.resolveImpersonation(1, tenant_constants_1.NEWTECH_CLIENT_ID, 2, 2)).rejects.toBeInstanceOf(common_1.UnauthorizedException);
100
+ }));
101
+ });
@@ -12,9 +12,19 @@ export interface TenantAuthorizationSnapshot {
12
12
  clientAlias: string;
13
13
  roles: TenantRoleAuthorization[];
14
14
  privileges: string[];
15
+ accessRole: TenantAccessRoleAuthorization;
16
+ permissions: string[];
17
+ authzVersion: number;
15
18
  identity: TenantAuthorizationIdentity;
16
19
  cachedAt: string;
17
20
  }
21
+ export interface TenantAccessRoleAuthorization {
22
+ id: number;
23
+ code: string;
24
+ name: string;
25
+ scope: 'GLOBAL' | 'CLIENT';
26
+ clientId: number | null;
27
+ }
18
28
  export interface TenantAuthorizationIdentity {
19
29
  id: number;
20
30
  homeClientId: number;
@@ -23,6 +23,15 @@ export interface AuthenticatedTenantUser {
23
23
  impersonationReason: string | null;
24
24
  roles: string[];
25
25
  privileges: string[];
26
+ role: {
27
+ id: number;
28
+ code: string;
29
+ name: string;
30
+ scope: 'GLOBAL' | 'CLIENT';
31
+ clientId: number | null;
32
+ };
33
+ permissions: string[];
34
+ authzVersion: number;
26
35
  isMasterTenantAdmin: boolean;
27
36
  first_name?: string;
28
37
  username?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniorganization/uni-lib",
3
- "version": "4.1.4",
3
+ "version": "4.3.0",
4
4
  "description": "UNI Library",
5
5
  "author": "Jhomiguel",
6
6
  "main": "dist/index.js",