@excofy/utils 2.0.0 → 2.1.1
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/.github/workflows/publish.yml +0 -3
- package/dist/index.cjs +45 -4
- package/dist/index.d.cts +22 -1
- package/dist/index.d.ts +22 -1
- package/dist/index.js +43 -4
- package/package.json +1 -1
- package/src/errors/crypto/CryptoError.ts +29 -0
- package/src/errors/index.ts +1 -0
- package/src/helpers/crypto.ts +61 -3
- package/src/index.ts +3 -0
package/dist/index.cjs
CHANGED
|
@@ -30,6 +30,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
ExpiredTokenError: () => ExpiredTokenError,
|
|
34
|
+
InvalidTokenError: () => InvalidTokenError,
|
|
33
35
|
createValidator: () => createValidator,
|
|
34
36
|
cryptoUtils: () => cryptoUtils,
|
|
35
37
|
generateCode: () => generateCode,
|
|
@@ -684,6 +686,26 @@ var stringUtils = {
|
|
|
684
686
|
removeFileExtension: (fileName) => fileName.replace(/\.[^/.]+$/, "")
|
|
685
687
|
};
|
|
686
688
|
|
|
689
|
+
// src/errors/crypto/CryptoError.ts
|
|
690
|
+
var CryptoError = class extends Error {
|
|
691
|
+
constructor(message) {
|
|
692
|
+
super(message);
|
|
693
|
+
this.name = "CryptoError";
|
|
694
|
+
}
|
|
695
|
+
};
|
|
696
|
+
var InvalidTokenError = class extends CryptoError {
|
|
697
|
+
constructor(message = "Invalid or malformed token") {
|
|
698
|
+
super(message);
|
|
699
|
+
this.name = "InvalidTokenError";
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
var ExpiredTokenError = class extends CryptoError {
|
|
703
|
+
constructor(message = "Token has expired") {
|
|
704
|
+
super(message);
|
|
705
|
+
this.name = "ExpiredTokenError";
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
|
|
687
709
|
// src/helpers/crypto.ts
|
|
688
710
|
var encoder = new TextEncoder();
|
|
689
711
|
var decoder = new TextDecoder();
|
|
@@ -729,9 +751,24 @@ var decryptPayload = async ({
|
|
|
729
751
|
);
|
|
730
752
|
return decoder.decode(buffer);
|
|
731
753
|
};
|
|
754
|
+
async function generateDeterministicHash(code, secret) {
|
|
755
|
+
const key = await crypto.subtle.importKey(
|
|
756
|
+
"raw",
|
|
757
|
+
encoder.encode(secret),
|
|
758
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
759
|
+
false,
|
|
760
|
+
["sign"]
|
|
761
|
+
);
|
|
762
|
+
const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(code));
|
|
763
|
+
return Array.from(new Uint8Array(signature)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
764
|
+
}
|
|
765
|
+
async function verifyDeterministicHash(inputCode, storedHash, secret) {
|
|
766
|
+
const hash = await generateDeterministicHash(inputCode, secret);
|
|
767
|
+
return hash === storedHash;
|
|
768
|
+
}
|
|
732
769
|
var cryptoUtils = {
|
|
733
770
|
uuidV4: () => crypto.randomUUID(),
|
|
734
|
-
hash: async (password, salt) => {
|
|
771
|
+
hash: async (password, salt = 10) => {
|
|
735
772
|
const importedKey = await crypto.subtle.importKey(
|
|
736
773
|
"raw",
|
|
737
774
|
encoder.encode(password),
|
|
@@ -794,11 +831,11 @@ var cryptoUtils = {
|
|
|
794
831
|
);
|
|
795
832
|
if (!valid) throw new Error("Invalid access token");
|
|
796
833
|
} catch (err) {
|
|
797
|
-
throw new
|
|
834
|
+
throw new InvalidTokenError("Invalid or malformed access token");
|
|
798
835
|
}
|
|
799
836
|
const { expiresAt } = JSON.parse(payloadDecrypted);
|
|
800
837
|
if (Date.now() > new Date(expiresAt).getTime()) {
|
|
801
|
-
throw new
|
|
838
|
+
throw new ExpiredTokenError("Access token has expired");
|
|
802
839
|
}
|
|
803
840
|
return payloadDecrypted;
|
|
804
841
|
},
|
|
@@ -807,7 +844,9 @@ var cryptoUtils = {
|
|
|
807
844
|
return Array.from(new Uint8Array(buffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
808
845
|
},
|
|
809
846
|
encryptPayload,
|
|
810
|
-
decryptPayload
|
|
847
|
+
decryptPayload,
|
|
848
|
+
generateDeterministicHash,
|
|
849
|
+
verifyDeterministicHash
|
|
811
850
|
};
|
|
812
851
|
|
|
813
852
|
// src/helpers/slug.ts
|
|
@@ -849,6 +888,8 @@ var toDecimal = (value) => {
|
|
|
849
888
|
};
|
|
850
889
|
// Annotate the CommonJS export names for ESM import in node:
|
|
851
890
|
0 && (module.exports = {
|
|
891
|
+
ExpiredTokenError,
|
|
892
|
+
InvalidTokenError,
|
|
852
893
|
createValidator,
|
|
853
894
|
cryptoUtils,
|
|
854
895
|
generateCode,
|
package/dist/index.d.cts
CHANGED
|
@@ -133,6 +133,8 @@ interface ICrypto {
|
|
|
133
133
|
AUTH_PAYLOAD_SECRET: string;
|
|
134
134
|
token: string;
|
|
135
135
|
}) => Promise<string>;
|
|
136
|
+
generateDeterministicHash: (code: string, secret: string) => Promise<string>;
|
|
137
|
+
verifyDeterministicHash: (inputCode: string, storedHash: string, secret: string) => Promise<boolean>;
|
|
136
138
|
}
|
|
137
139
|
declare const cryptoUtils: ICrypto;
|
|
138
140
|
|
|
@@ -198,4 +200,23 @@ declare namespace number {
|
|
|
198
200
|
export { number_toCents as toCents, number_toDecimal as toDecimal };
|
|
199
201
|
}
|
|
200
202
|
|
|
201
|
-
|
|
203
|
+
/**
|
|
204
|
+
* Classe base para erros de criptografia
|
|
205
|
+
*/
|
|
206
|
+
declare class CryptoError extends Error {
|
|
207
|
+
constructor(message: string);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Token inválido, malformado ou corrompido
|
|
211
|
+
*/
|
|
212
|
+
declare class InvalidTokenError extends CryptoError {
|
|
213
|
+
constructor(message?: string);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Token válido mas expirado
|
|
217
|
+
*/
|
|
218
|
+
declare class ExpiredTokenError extends CryptoError {
|
|
219
|
+
constructor(message?: string);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export { ExpiredTokenError, InvalidTokenError, createValidator, cryptoUtils, generateCode, htmlEntityDecode, number as numberUtils, slug as slugUtils, stringUtils };
|
package/dist/index.d.ts
CHANGED
|
@@ -133,6 +133,8 @@ interface ICrypto {
|
|
|
133
133
|
AUTH_PAYLOAD_SECRET: string;
|
|
134
134
|
token: string;
|
|
135
135
|
}) => Promise<string>;
|
|
136
|
+
generateDeterministicHash: (code: string, secret: string) => Promise<string>;
|
|
137
|
+
verifyDeterministicHash: (inputCode: string, storedHash: string, secret: string) => Promise<boolean>;
|
|
136
138
|
}
|
|
137
139
|
declare const cryptoUtils: ICrypto;
|
|
138
140
|
|
|
@@ -198,4 +200,23 @@ declare namespace number {
|
|
|
198
200
|
export { number_toCents as toCents, number_toDecimal as toDecimal };
|
|
199
201
|
}
|
|
200
202
|
|
|
201
|
-
|
|
203
|
+
/**
|
|
204
|
+
* Classe base para erros de criptografia
|
|
205
|
+
*/
|
|
206
|
+
declare class CryptoError extends Error {
|
|
207
|
+
constructor(message: string);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Token inválido, malformado ou corrompido
|
|
211
|
+
*/
|
|
212
|
+
declare class InvalidTokenError extends CryptoError {
|
|
213
|
+
constructor(message?: string);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Token válido mas expirado
|
|
217
|
+
*/
|
|
218
|
+
declare class ExpiredTokenError extends CryptoError {
|
|
219
|
+
constructor(message?: string);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export { ExpiredTokenError, InvalidTokenError, createValidator, cryptoUtils, generateCode, htmlEntityDecode, number as numberUtils, slug as slugUtils, stringUtils };
|
package/dist/index.js
CHANGED
|
@@ -648,6 +648,26 @@ var stringUtils = {
|
|
|
648
648
|
removeFileExtension: (fileName) => fileName.replace(/\.[^/.]+$/, "")
|
|
649
649
|
};
|
|
650
650
|
|
|
651
|
+
// src/errors/crypto/CryptoError.ts
|
|
652
|
+
var CryptoError = class extends Error {
|
|
653
|
+
constructor(message) {
|
|
654
|
+
super(message);
|
|
655
|
+
this.name = "CryptoError";
|
|
656
|
+
}
|
|
657
|
+
};
|
|
658
|
+
var InvalidTokenError = class extends CryptoError {
|
|
659
|
+
constructor(message = "Invalid or malformed token") {
|
|
660
|
+
super(message);
|
|
661
|
+
this.name = "InvalidTokenError";
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
var ExpiredTokenError = class extends CryptoError {
|
|
665
|
+
constructor(message = "Token has expired") {
|
|
666
|
+
super(message);
|
|
667
|
+
this.name = "ExpiredTokenError";
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
|
|
651
671
|
// src/helpers/crypto.ts
|
|
652
672
|
var encoder = new TextEncoder();
|
|
653
673
|
var decoder = new TextDecoder();
|
|
@@ -693,9 +713,24 @@ var decryptPayload = async ({
|
|
|
693
713
|
);
|
|
694
714
|
return decoder.decode(buffer);
|
|
695
715
|
};
|
|
716
|
+
async function generateDeterministicHash(code, secret) {
|
|
717
|
+
const key = await crypto.subtle.importKey(
|
|
718
|
+
"raw",
|
|
719
|
+
encoder.encode(secret),
|
|
720
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
721
|
+
false,
|
|
722
|
+
["sign"]
|
|
723
|
+
);
|
|
724
|
+
const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(code));
|
|
725
|
+
return Array.from(new Uint8Array(signature)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
726
|
+
}
|
|
727
|
+
async function verifyDeterministicHash(inputCode, storedHash, secret) {
|
|
728
|
+
const hash = await generateDeterministicHash(inputCode, secret);
|
|
729
|
+
return hash === storedHash;
|
|
730
|
+
}
|
|
696
731
|
var cryptoUtils = {
|
|
697
732
|
uuidV4: () => crypto.randomUUID(),
|
|
698
|
-
hash: async (password, salt) => {
|
|
733
|
+
hash: async (password, salt = 10) => {
|
|
699
734
|
const importedKey = await crypto.subtle.importKey(
|
|
700
735
|
"raw",
|
|
701
736
|
encoder.encode(password),
|
|
@@ -758,11 +793,11 @@ var cryptoUtils = {
|
|
|
758
793
|
);
|
|
759
794
|
if (!valid) throw new Error("Invalid access token");
|
|
760
795
|
} catch (err) {
|
|
761
|
-
throw new
|
|
796
|
+
throw new InvalidTokenError("Invalid or malformed access token");
|
|
762
797
|
}
|
|
763
798
|
const { expiresAt } = JSON.parse(payloadDecrypted);
|
|
764
799
|
if (Date.now() > new Date(expiresAt).getTime()) {
|
|
765
|
-
throw new
|
|
800
|
+
throw new ExpiredTokenError("Access token has expired");
|
|
766
801
|
}
|
|
767
802
|
return payloadDecrypted;
|
|
768
803
|
},
|
|
@@ -771,7 +806,9 @@ var cryptoUtils = {
|
|
|
771
806
|
return Array.from(new Uint8Array(buffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
772
807
|
},
|
|
773
808
|
encryptPayload,
|
|
774
|
-
decryptPayload
|
|
809
|
+
decryptPayload,
|
|
810
|
+
generateDeterministicHash,
|
|
811
|
+
verifyDeterministicHash
|
|
775
812
|
};
|
|
776
813
|
|
|
777
814
|
// src/helpers/slug.ts
|
|
@@ -812,6 +849,8 @@ var toDecimal = (value) => {
|
|
|
812
849
|
return new Big(value).div(100).round(2).toNumber();
|
|
813
850
|
};
|
|
814
851
|
export {
|
|
852
|
+
ExpiredTokenError,
|
|
853
|
+
InvalidTokenError,
|
|
815
854
|
createValidator,
|
|
816
855
|
cryptoUtils,
|
|
817
856
|
generateCode,
|
package/package.json
CHANGED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Classe base para erros de criptografia
|
|
3
|
+
*/
|
|
4
|
+
export class CryptoError extends Error {
|
|
5
|
+
constructor(message: string) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = 'CryptoError';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Token inválido, malformado ou corrompido
|
|
13
|
+
*/
|
|
14
|
+
export class InvalidTokenError extends CryptoError {
|
|
15
|
+
constructor(message: string = 'Invalid or malformed token') {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = 'InvalidTokenError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Token válido mas expirado
|
|
23
|
+
*/
|
|
24
|
+
export class ExpiredTokenError extends CryptoError {
|
|
25
|
+
constructor(message: string = 'Token has expired') {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = 'ExpiredTokenError';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { InvalidTokenError, ExpiredTokenError } from './crypto/CryptoError';
|
package/src/helpers/crypto.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ExpiredTokenError,
|
|
3
|
+
InvalidTokenError,
|
|
4
|
+
} from '../errors/crypto/CryptoError';
|
|
5
|
+
|
|
1
6
|
interface IGenerateAccessToken {
|
|
2
7
|
AUTH_SIGN_SECRET: string;
|
|
3
8
|
AUTH_PAYLOAD_SECRET: string;
|
|
@@ -25,6 +30,12 @@ interface ICrypto {
|
|
|
25
30
|
AUTH_PAYLOAD_SECRET: string;
|
|
26
31
|
token: string;
|
|
27
32
|
}) => Promise<string>;
|
|
33
|
+
generateDeterministicHash: (code: string, secret: string) => Promise<string>;
|
|
34
|
+
verifyDeterministicHash: (
|
|
35
|
+
inputCode: string,
|
|
36
|
+
storedHash: string,
|
|
37
|
+
secret: string
|
|
38
|
+
) => Promise<boolean>;
|
|
28
39
|
}
|
|
29
40
|
|
|
30
41
|
/* --------------------- Helpers básicos --------------------- */
|
|
@@ -94,11 +105,55 @@ const decryptPayload = async ({
|
|
|
94
105
|
return decoder.decode(buffer);
|
|
95
106
|
};
|
|
96
107
|
|
|
108
|
+
/* --------------------- Hash (generate/verify) --------------------- */
|
|
109
|
+
/**
|
|
110
|
+
* Gera um hash determinístico (HMAC) a partir de um código e um segredo.
|
|
111
|
+
*
|
|
112
|
+
* @param code
|
|
113
|
+
* @param secret
|
|
114
|
+
* @returns
|
|
115
|
+
*/
|
|
116
|
+
export async function generateDeterministicHash(
|
|
117
|
+
code: string,
|
|
118
|
+
secret: string
|
|
119
|
+
): Promise<string> {
|
|
120
|
+
const key = await crypto.subtle.importKey(
|
|
121
|
+
'raw',
|
|
122
|
+
encoder.encode(secret),
|
|
123
|
+
{ name: 'HMAC', hash: 'SHA-256' },
|
|
124
|
+
false,
|
|
125
|
+
['sign']
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const signature = await crypto.subtle.sign('HMAC', key, encoder.encode(code));
|
|
129
|
+
|
|
130
|
+
return Array.from(new Uint8Array(signature))
|
|
131
|
+
.map((b) => b.toString(16).padStart(2, '0'))
|
|
132
|
+
.join('');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Verifica se o hash gerado a partir do código e segredo bate com o hash armazenado.
|
|
137
|
+
*
|
|
138
|
+
* @param inputCode
|
|
139
|
+
* @param storedHash
|
|
140
|
+
* @param secret
|
|
141
|
+
* @returns
|
|
142
|
+
*/
|
|
143
|
+
export async function verifyDeterministicHash(
|
|
144
|
+
inputCode: string,
|
|
145
|
+
storedHash: string,
|
|
146
|
+
secret: string
|
|
147
|
+
): Promise<boolean> {
|
|
148
|
+
const hash = await generateDeterministicHash(inputCode, secret);
|
|
149
|
+
return hash === storedHash;
|
|
150
|
+
}
|
|
151
|
+
|
|
97
152
|
/* --------------------- Main API --------------------- */
|
|
98
153
|
export const cryptoUtils: ICrypto = {
|
|
99
154
|
uuidV4: (): string => crypto.randomUUID(),
|
|
100
155
|
|
|
101
|
-
hash: async (password: string, salt: number): Promise<string> => {
|
|
156
|
+
hash: async (password: string, salt: number = 10): Promise<string> => {
|
|
102
157
|
const importedKey = await crypto.subtle.importKey(
|
|
103
158
|
'raw',
|
|
104
159
|
encoder.encode(password),
|
|
@@ -178,13 +233,13 @@ export const cryptoUtils: ICrypto = {
|
|
|
178
233
|
if (!valid) throw new Error('Invalid access token');
|
|
179
234
|
} catch (err) {
|
|
180
235
|
// qualquer falha na decriptação ou verificação -> token inválido
|
|
181
|
-
throw new
|
|
236
|
+
throw new InvalidTokenError('Invalid or malformed access token');
|
|
182
237
|
}
|
|
183
238
|
|
|
184
239
|
// verifica expiração **fora do try/catch**, para não ser capturada como token inválido
|
|
185
240
|
const { expiresAt } = JSON.parse(payloadDecrypted);
|
|
186
241
|
if (Date.now() > new Date(expiresAt).getTime()) {
|
|
187
|
-
throw new
|
|
242
|
+
throw new ExpiredTokenError('Access token has expired');
|
|
188
243
|
}
|
|
189
244
|
|
|
190
245
|
return payloadDecrypted;
|
|
@@ -199,4 +254,7 @@ export const cryptoUtils: ICrypto = {
|
|
|
199
254
|
|
|
200
255
|
encryptPayload,
|
|
201
256
|
decryptPayload,
|
|
257
|
+
|
|
258
|
+
generateDeterministicHash,
|
|
259
|
+
verifyDeterministicHash,
|
|
202
260
|
};
|