@excofy/utils 2.1.2 → 2.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.
package/dist/index.cjs CHANGED
@@ -234,6 +234,8 @@ function createValidator() {
234
234
  isValidType = typeof value === "object" && value !== null && !Array.isArray(value);
235
235
  } else if (type === "file") {
236
236
  isValidType = value instanceof File;
237
+ } else if (type === "base64") {
238
+ isValidType = typeof value === "string" && !!value.match(/^data:([A-Za-z-+\/]+);base64,([A-Za-z0-9+/=]+)$/);
237
239
  } else if (type === "date") {
238
240
  isValidType = typeof value === "string" && !Number.isNaN(new Date(value).getTime());
239
241
  } else {
@@ -250,6 +252,42 @@ function createValidator() {
250
252
  return !current.required && (current.value === void 0 || current.value === null || current.value === "");
251
253
  };
252
254
  const validator = {
255
+ base64Type(validTypes, message) {
256
+ if (shouldSkipValidation()) return validator;
257
+ const value = current.value;
258
+ if (typeof value !== "string") {
259
+ current.pushError(message);
260
+ return validator;
261
+ }
262
+ const match = value.match(/^data:([A-Za-z-+\/]+);base64,/);
263
+ if (!match) {
264
+ current.pushError(message);
265
+ return validator;
266
+ }
267
+ const mimeType = match[1];
268
+ if (!validTypes.includes(mimeType)) {
269
+ current.pushError(message);
270
+ }
271
+ return validator;
272
+ },
273
+ base64MaxSize(maxSize, message) {
274
+ if (shouldSkipValidation()) return validator;
275
+ const value = current.value;
276
+ if (typeof value !== "string") {
277
+ current.pushError(message);
278
+ return validator;
279
+ }
280
+ const base64Content = value.split(",")[1];
281
+ if (!base64Content) {
282
+ current.pushError(message);
283
+ return validator;
284
+ }
285
+ const sizeInBytes = Math.ceil(base64Content.length * 3 / 4);
286
+ if (sizeInBytes > maxSize) {
287
+ current.pushError(message);
288
+ }
289
+ return validator;
290
+ },
253
291
  cnpj: (message) => {
254
292
  if (shouldSkipValidation()) {
255
293
  return validator;
package/dist/index.d.cts CHANGED
@@ -13,11 +13,13 @@ type TMessage = {
13
13
  };
14
14
  type TValue = string | number | boolean | File | object | null | undefined;
15
15
  type TInputValue = TValue;
16
- type TTypes = 'string' | 'number' | 'boolean' | 'object' | 'array' | 'file' | 'date';
16
+ type TTypes = 'string' | 'number' | 'boolean' | 'object' | 'array' | 'file' | 'base64' | 'date';
17
17
  interface IInputErrors {
18
18
  [key: string]: TMessage[] | IInputErrors[];
19
19
  }
20
20
  interface ValidatorField {
21
+ base64Type(validTypes: string[], message: string): ValidatorField;
22
+ base64MaxSize(maxSize: number, message: string): ValidatorField;
21
23
  cnpj(message: string): ValidatorField;
22
24
  cpf(message: string): ValidatorField;
23
25
  each(callback: <U extends Record<string, TInputValue>>(item: U, index: number, subValidator: ReturnType<typeof createValidator<U>>) => void): ValidatorField;
package/dist/index.d.ts CHANGED
@@ -13,11 +13,13 @@ type TMessage = {
13
13
  };
14
14
  type TValue = string | number | boolean | File | object | null | undefined;
15
15
  type TInputValue = TValue;
16
- type TTypes = 'string' | 'number' | 'boolean' | 'object' | 'array' | 'file' | 'date';
16
+ type TTypes = 'string' | 'number' | 'boolean' | 'object' | 'array' | 'file' | 'base64' | 'date';
17
17
  interface IInputErrors {
18
18
  [key: string]: TMessage[] | IInputErrors[];
19
19
  }
20
20
  interface ValidatorField {
21
+ base64Type(validTypes: string[], message: string): ValidatorField;
22
+ base64MaxSize(maxSize: number, message: string): ValidatorField;
21
23
  cnpj(message: string): ValidatorField;
22
24
  cpf(message: string): ValidatorField;
23
25
  each(callback: <U extends Record<string, TInputValue>>(item: U, index: number, subValidator: ReturnType<typeof createValidator<U>>) => void): ValidatorField;
package/dist/index.js CHANGED
@@ -196,6 +196,8 @@ function createValidator() {
196
196
  isValidType = typeof value === "object" && value !== null && !Array.isArray(value);
197
197
  } else if (type === "file") {
198
198
  isValidType = value instanceof File;
199
+ } else if (type === "base64") {
200
+ isValidType = typeof value === "string" && !!value.match(/^data:([A-Za-z-+\/]+);base64,([A-Za-z0-9+/=]+)$/);
199
201
  } else if (type === "date") {
200
202
  isValidType = typeof value === "string" && !Number.isNaN(new Date(value).getTime());
201
203
  } else {
@@ -212,6 +214,42 @@ function createValidator() {
212
214
  return !current.required && (current.value === void 0 || current.value === null || current.value === "");
213
215
  };
214
216
  const validator = {
217
+ base64Type(validTypes, message) {
218
+ if (shouldSkipValidation()) return validator;
219
+ const value = current.value;
220
+ if (typeof value !== "string") {
221
+ current.pushError(message);
222
+ return validator;
223
+ }
224
+ const match = value.match(/^data:([A-Za-z-+\/]+);base64,/);
225
+ if (!match) {
226
+ current.pushError(message);
227
+ return validator;
228
+ }
229
+ const mimeType = match[1];
230
+ if (!validTypes.includes(mimeType)) {
231
+ current.pushError(message);
232
+ }
233
+ return validator;
234
+ },
235
+ base64MaxSize(maxSize, message) {
236
+ if (shouldSkipValidation()) return validator;
237
+ const value = current.value;
238
+ if (typeof value !== "string") {
239
+ current.pushError(message);
240
+ return validator;
241
+ }
242
+ const base64Content = value.split(",")[1];
243
+ if (!base64Content) {
244
+ current.pushError(message);
245
+ return validator;
246
+ }
247
+ const sizeInBytes = Math.ceil(base64Content.length * 3 / 4);
248
+ if (sizeInBytes > maxSize) {
249
+ current.pushError(message);
250
+ }
251
+ return validator;
252
+ },
215
253
  cnpj: (message) => {
216
254
  if (shouldSkipValidation()) {
217
255
  return validator;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@excofy/utils",
3
- "version": "2.1.2",
3
+ "version": "2.3.0",
4
4
  "description": "Biblioteca de utilitários para o Excofy",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -16,7 +16,7 @@
16
16
  "typecheck": "tsc --noEmit",
17
17
  "prepublishOnly": "npm run build",
18
18
  "deploy": "act",
19
- "test": "tsx --test 'tests/**/*.spec.ts' --testTimeout=10000"
19
+ "test": "tsx --test 'tests/helpers/crypto.spec.ts' --testTimeout=10000"
20
20
  },
21
21
  "repository": {
22
22
  "type": "git",
@@ -153,7 +153,7 @@ export async function verifyDeterministicHash(
153
153
  export const cryptoUtils: ICrypto = {
154
154
  uuidV4: (): string => crypto.randomUUID(),
155
155
 
156
- hash: async (password: string, salt: number = 10): Promise<string> => {
156
+ hash: async (password: string, salt = 10): Promise<string> => {
157
157
  const importedKey = await crypto.subtle.importKey(
158
158
  'raw',
159
159
  encoder.encode(password),
@@ -0,0 +1,28 @@
1
+ export const fileUtils = {
2
+ base64ToFile: (base64: string | null, filename: string): File | null => {
3
+ if (!base64 || base64 === '') {
4
+ return null;
5
+ }
6
+
7
+ const arr = base64.split(',');
8
+ if (arr.length !== 2) {
9
+ return null;
10
+ }
11
+
12
+ const mime = arr[0].match(/:(.*?);/)?.[1];
13
+
14
+ if (!mime) {
15
+ return null;
16
+ }
17
+
18
+ const bstr = atob(arr[1]);
19
+ let n = bstr.length;
20
+ const u8arr = new Uint8Array(n);
21
+
22
+ while (n--) {
23
+ u8arr[n] = bstr.charCodeAt(n);
24
+ }
25
+
26
+ return new File([u8arr], filename, { type: mime });
27
+ },
28
+ };
@@ -1,4 +1,5 @@
1
1
  import { isValidCNPJ, isValidCPF } from './document';
2
+ import { fileUtils } from './file';
2
3
  import { sanitizeValue, type AllowedTag } from './sanitize';
3
4
  import { video } from './video';
4
5
 
@@ -12,6 +13,7 @@ type TTypes =
12
13
  | 'object'
13
14
  | 'array'
14
15
  | 'file'
16
+ | 'base64'
15
17
  | 'date';
16
18
 
17
19
  interface IInputErrors {
@@ -19,6 +21,8 @@ interface IInputErrors {
19
21
  }
20
22
 
21
23
  interface ValidatorField {
24
+ base64Type(validTypes: string[], message: string): ValidatorField;
25
+ base64MaxSize(maxSize: number, message: string): ValidatorField;
22
26
  cnpj(message: string): ValidatorField;
23
27
  cpf(message: string): ValidatorField;
24
28
  each(
@@ -139,6 +143,10 @@ export function createValidator<
139
143
  typeof value === 'object' && value !== null && !Array.isArray(value);
140
144
  } else if (type === 'file') {
141
145
  isValidType = value instanceof File;
146
+ } else if (type === 'base64') {
147
+ isValidType =
148
+ typeof value === 'string' &&
149
+ !!value.match(/^data:([A-Za-z-+\/]+);base64,([A-Za-z0-9+/=]+)$/);
142
150
  } else if (type === 'date') {
143
151
  isValidType =
144
152
  typeof value === 'string' && !Number.isNaN(new Date(value).getTime());
@@ -169,6 +177,55 @@ export function createValidator<
169
177
  };
170
178
 
171
179
  const validator: ValidatorField = {
180
+ base64Type(validTypes: string[], message: string) {
181
+ if (shouldSkipValidation()) return validator;
182
+
183
+ const value = current.value;
184
+
185
+ if (typeof value !== 'string') {
186
+ current.pushError(message);
187
+ return validator;
188
+ }
189
+
190
+ const match = value.match(/^data:([A-Za-z-+\/]+);base64,/);
191
+ if (!match) {
192
+ current.pushError(message);
193
+ return validator;
194
+ }
195
+
196
+ const mimeType = match[1];
197
+ if (!validTypes.includes(mimeType)) {
198
+ current.pushError(message);
199
+ }
200
+
201
+ return validator;
202
+ },
203
+
204
+ base64MaxSize(maxSize: number, message: string) {
205
+ if (shouldSkipValidation()) return validator;
206
+
207
+ const value = current.value;
208
+
209
+ if (typeof value !== 'string') {
210
+ current.pushError(message);
211
+ return validator;
212
+ }
213
+
214
+ const base64Content = value.split(',')[1];
215
+ if (!base64Content) {
216
+ current.pushError(message);
217
+ return validator;
218
+ }
219
+
220
+ // Calcula o tamanho do arquivo em bytes
221
+ const sizeInBytes = Math.ceil((base64Content.length * 3) / 4);
222
+ if (sizeInBytes > maxSize) {
223
+ current.pushError(message);
224
+ }
225
+
226
+ return validator;
227
+ },
228
+
172
229
  cnpj: (message: string) => {
173
230
  if (shouldSkipValidation()) {
174
231
  return validator;
@@ -0,0 +1,22 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { cryptoUtils } from '../../src/helpers/crypto';
4
+
5
+ const AUTH_PAYLOAD_SECRET = '98c026b8-80d7-43ac-80d0-5118cb9c0102';
6
+ const code = 'N3HDL5';
7
+
8
+ describe('cryptoUtils', () => {
9
+ it('uuidV4 deve gerar um UUID válido', async () => {
10
+ const encrypted = await cryptoUtils.encryptPayload({
11
+ AUTH_PAYLOAD_SECRET,
12
+ payload: code,
13
+ });
14
+
15
+ const hash = await cryptoUtils.generateDeterministicHash(
16
+ code,
17
+ AUTH_PAYLOAD_SECRET
18
+ );
19
+
20
+ console.log({ encrypted, hash });
21
+ });
22
+ });