revojs 0.2.42 → 0.2.43
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/crypto/index.d.ts +30 -0
- package/dist/crypto/index.js +136 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -1
- package/dist/module/index.js +4 -0
- package/dist/package.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type Scope } from "../scope";
|
|
2
|
+
export type CryptoPurpose = string;
|
|
3
|
+
export type SecretAlgorithm = "HMAC" | "AES-GCM";
|
|
4
|
+
export declare function useSecretKey(scope: Scope, purpose: CryptoPurpose, algorithm: SecretAlgorithm): Promise<CryptoKey>;
|
|
5
|
+
export declare function sign(scope: Scope, purpose: CryptoPurpose, value: string | Uint8Array): Promise<string>;
|
|
6
|
+
export declare function verify(scope: Scope, purpose: CryptoPurpose, value: string | Uint8Array, signature: string): Promise<boolean>;
|
|
7
|
+
export declare function encrypt(scope: Scope, purpose: CryptoPurpose, value: string, context?: string): Promise<string>;
|
|
8
|
+
export declare function decrypt(scope: Scope, purpose: CryptoPurpose, value: string, context?: string): Promise<string | undefined>;
|
|
9
|
+
export declare function hashPassword(password: string): Promise<string>;
|
|
10
|
+
export declare function verifyPassword(password: string, value: string): Promise<boolean>;
|
|
11
|
+
export declare function isPasswordHash(value: string): boolean;
|
|
12
|
+
export declare function needsPasswordRehash(value: string): boolean;
|
|
13
|
+
export declare function randomToken(length?: number): string;
|
|
14
|
+
export declare function digest(value: string | Uint8Array): Promise<string>;
|
|
15
|
+
export declare function equalsConstantTime(left: string | Uint8Array, right: string | Uint8Array): boolean;
|
|
16
|
+
export declare function derivePassword(password: string, salt: Uint8Array<ArrayBuffer>, iterations: number): Promise<Uint8Array<ArrayBuffer>>;
|
|
17
|
+
export declare function parsePasswordHash(value: string): {
|
|
18
|
+
iterations: number;
|
|
19
|
+
salt: Uint8Array<ArrayBuffer>;
|
|
20
|
+
hash: Uint8Array<ArrayBuffer>;
|
|
21
|
+
} | undefined;
|
|
22
|
+
export declare function toBytes(value: string | Uint8Array): Uint8Array<ArrayBuffer>;
|
|
23
|
+
export declare const encoder: TextEncoder;
|
|
24
|
+
export declare const decoder: TextDecoder;
|
|
25
|
+
export declare const BASE64_ENCODE: Parameters<Uint8Array["toBase64"]>[0];
|
|
26
|
+
export declare const BASE64_DECODE: Parameters<typeof Uint8Array.fromBase64>[1];
|
|
27
|
+
export declare const MIN_SECRET_LENGTH = 32;
|
|
28
|
+
export declare const ENCRYPTION_ALGORITHM = "aes-gcm";
|
|
29
|
+
export declare const PASSWORD_ALGORITHM = "pbkdf2-sha256";
|
|
30
|
+
export declare const PASSWORD_ITERATIONS = 100000;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { useVariables } from "../scope/index.js";
|
|
2
|
+
//#region src/crypto/index.ts
|
|
3
|
+
async function useSecretKey(scope, purpose, algorithm) {
|
|
4
|
+
const { applicationSecret } = useVariables(scope);
|
|
5
|
+
if (!applicationSecret) throw new Error("Missing required variable \"applicationSecret\"");
|
|
6
|
+
if (applicationSecret.length < 32) throw new Error(`The variable "applicationSecret" must be at least 32 characters long.`);
|
|
7
|
+
const material = await crypto.subtle.importKey("raw", encoder.encode(applicationSecret), "HKDF", false, ["deriveKey"]);
|
|
8
|
+
return crypto.subtle.deriveKey({
|
|
9
|
+
name: "HKDF",
|
|
10
|
+
hash: "SHA-256",
|
|
11
|
+
salt: encoder.encode("revojs"),
|
|
12
|
+
info: encoder.encode(purpose)
|
|
13
|
+
}, material, algorithm === "HMAC" ? {
|
|
14
|
+
name: "HMAC",
|
|
15
|
+
hash: "SHA-256",
|
|
16
|
+
length: 256
|
|
17
|
+
} : {
|
|
18
|
+
name: "AES-GCM",
|
|
19
|
+
length: 256
|
|
20
|
+
}, false, algorithm === "HMAC" ? ["sign", "verify"] : ["encrypt", "decrypt"]);
|
|
21
|
+
}
|
|
22
|
+
async function sign(scope, purpose, value) {
|
|
23
|
+
const key = await useSecretKey(scope, purpose, "HMAC");
|
|
24
|
+
return new Uint8Array(await crypto.subtle.sign("HMAC", key, toBytes(value))).toBase64(BASE64_ENCODE);
|
|
25
|
+
}
|
|
26
|
+
async function verify(scope, purpose, value, signature) {
|
|
27
|
+
const key = await useSecretKey(scope, purpose, "HMAC");
|
|
28
|
+
try {
|
|
29
|
+
return await crypto.subtle.verify("HMAC", key, Uint8Array.fromBase64(signature, BASE64_DECODE), toBytes(value));
|
|
30
|
+
} catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function encrypt(scope, purpose, value, context = "") {
|
|
35
|
+
const key = await useSecretKey(scope, purpose, "AES-GCM");
|
|
36
|
+
const iv = crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(12));
|
|
37
|
+
const ciphertext = await crypto.subtle.encrypt({
|
|
38
|
+
name: "AES-GCM",
|
|
39
|
+
iv,
|
|
40
|
+
additionalData: encoder.encode(context)
|
|
41
|
+
}, key, encoder.encode(value));
|
|
42
|
+
return [
|
|
43
|
+
ENCRYPTION_ALGORITHM,
|
|
44
|
+
iv.toBase64(BASE64_ENCODE),
|
|
45
|
+
new Uint8Array(ciphertext).toBase64(BASE64_ENCODE)
|
|
46
|
+
].join("$");
|
|
47
|
+
}
|
|
48
|
+
async function decrypt(scope, purpose, value, context = "") {
|
|
49
|
+
const [algorithm, iv, ciphertext, ...rest] = value.split("$");
|
|
50
|
+
if (algorithm !== "aes-gcm" || !iv || !ciphertext || rest.length > 0) return;
|
|
51
|
+
const key = await useSecretKey(scope, purpose, "AES-GCM");
|
|
52
|
+
try {
|
|
53
|
+
const plaintext = await crypto.subtle.decrypt({
|
|
54
|
+
name: "AES-GCM",
|
|
55
|
+
iv: Uint8Array.fromBase64(iv, BASE64_DECODE),
|
|
56
|
+
additionalData: encoder.encode(context)
|
|
57
|
+
}, key, Uint8Array.fromBase64(ciphertext, BASE64_DECODE));
|
|
58
|
+
return decoder.decode(plaintext);
|
|
59
|
+
} catch {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function hashPassword(password) {
|
|
64
|
+
const salt = crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(16));
|
|
65
|
+
const hash = await derivePassword(password, salt, PASSWORD_ITERATIONS);
|
|
66
|
+
return [
|
|
67
|
+
PASSWORD_ALGORITHM,
|
|
68
|
+
PASSWORD_ITERATIONS,
|
|
69
|
+
salt.toBase64(BASE64_ENCODE),
|
|
70
|
+
hash.toBase64(BASE64_ENCODE)
|
|
71
|
+
].join("$");
|
|
72
|
+
}
|
|
73
|
+
async function verifyPassword(password, value) {
|
|
74
|
+
const parsed = parsePasswordHash(value);
|
|
75
|
+
if (!parsed) return false;
|
|
76
|
+
return equalsConstantTime(await derivePassword(password, parsed.salt, parsed.iterations), parsed.hash);
|
|
77
|
+
}
|
|
78
|
+
function isPasswordHash(value) {
|
|
79
|
+
return parsePasswordHash(value) !== void 0;
|
|
80
|
+
}
|
|
81
|
+
function needsPasswordRehash(value) {
|
|
82
|
+
return parsePasswordHash(value)?.iterations !== PASSWORD_ITERATIONS;
|
|
83
|
+
}
|
|
84
|
+
function randomToken(length = 32) {
|
|
85
|
+
return crypto.getRandomValues(new Uint8Array(length)).toBase64(BASE64_ENCODE);
|
|
86
|
+
}
|
|
87
|
+
async function digest(value) {
|
|
88
|
+
return new Uint8Array(await crypto.subtle.digest("SHA-256", toBytes(value))).toHex();
|
|
89
|
+
}
|
|
90
|
+
function equalsConstantTime(left, right) {
|
|
91
|
+
const leftBytes = toBytes(left);
|
|
92
|
+
const rightBytes = toBytes(right);
|
|
93
|
+
let difference = leftBytes.length ^ rightBytes.length;
|
|
94
|
+
for (let index = 0; index < Math.max(leftBytes.length, rightBytes.length); index++) difference |= (leftBytes[index] ?? 0) ^ (rightBytes[index] ?? 0);
|
|
95
|
+
return difference === 0;
|
|
96
|
+
}
|
|
97
|
+
async function derivePassword(password, salt, iterations) {
|
|
98
|
+
const key = await crypto.subtle.importKey("raw", encoder.encode(password.normalize("NFKC")), "PBKDF2", false, ["deriveBits"]);
|
|
99
|
+
return new Uint8Array(await crypto.subtle.deriveBits({
|
|
100
|
+
name: "PBKDF2",
|
|
101
|
+
hash: "SHA-256",
|
|
102
|
+
salt,
|
|
103
|
+
iterations
|
|
104
|
+
}, key, 256));
|
|
105
|
+
}
|
|
106
|
+
function parsePasswordHash(value) {
|
|
107
|
+
const [algorithm, iterations, salt, hash, ...rest] = value.split("$");
|
|
108
|
+
if (algorithm !== "pbkdf2-sha256" || !iterations || !salt || !hash || rest.length > 0) return;
|
|
109
|
+
const count = Number(iterations);
|
|
110
|
+
if (!Number.isInteger(count) || count < 1 || count > 1e5) return;
|
|
111
|
+
try {
|
|
112
|
+
return {
|
|
113
|
+
iterations: count,
|
|
114
|
+
salt: Uint8Array.fromBase64(salt, BASE64_DECODE),
|
|
115
|
+
hash: Uint8Array.fromBase64(hash, BASE64_DECODE)
|
|
116
|
+
};
|
|
117
|
+
} catch {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function toBytes(value) {
|
|
122
|
+
return typeof value === "string" ? encoder.encode(value) : new Uint8Array(value);
|
|
123
|
+
}
|
|
124
|
+
var encoder = new TextEncoder();
|
|
125
|
+
var decoder = new TextDecoder();
|
|
126
|
+
var BASE64_ENCODE = {
|
|
127
|
+
alphabet: "base64url",
|
|
128
|
+
omitPadding: true
|
|
129
|
+
};
|
|
130
|
+
var BASE64_DECODE = { alphabet: "base64url" };
|
|
131
|
+
var MIN_SECRET_LENGTH = 32;
|
|
132
|
+
var ENCRYPTION_ALGORITHM = "aes-gcm";
|
|
133
|
+
var PASSWORD_ALGORITHM = "pbkdf2-sha256";
|
|
134
|
+
var PASSWORD_ITERATIONS = 1e5;
|
|
135
|
+
//#endregion
|
|
136
|
+
export { BASE64_DECODE, BASE64_ENCODE, ENCRYPTION_ALGORITHM, MIN_SECRET_LENGTH, PASSWORD_ALGORITHM, PASSWORD_ITERATIONS, decoder, decrypt, derivePassword, digest, encoder, encrypt, equalsConstantTime, hashPassword, isPasswordHash, needsPasswordRehash, parsePasswordHash, randomToken, sign, toBytes, useSecretKey, verify, verifyPassword };
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { mimeType, mimeTypes } from "./assets/index.js";
|
|
2
2
|
import { STATE_CONTEXT, VARIABLE_CONTEXT, createScope, defineContext, defineHook, definePlugin, dispatchHook, dispatchHookSingle, getContext, registerHook, registerHooks, setContext, useVariableContext, useVariables } from "./scope/index.js";
|
|
3
|
+
import { BASE64_DECODE, BASE64_ENCODE, ENCRYPTION_ALGORITHM, MIN_SECRET_LENGTH, PASSWORD_ALGORITHM, PASSWORD_ITERATIONS, decoder, decrypt, derivePassword, digest, encoder, encrypt, equalsConstantTime, hashPassword, isPasswordHash, needsPasswordRehash, parsePasswordHash, randomToken, sign, toBytes, useSecretKey, verify, verifyPassword } from "./crypto/index.js";
|
|
3
4
|
import { GROUP_MATCH, HOOK_MATCH, OPTIONAL_PARAMETER_MATCH, OPTIONAL_WILDCARD_MATCH, PARAMETER_MATCH, ROUTE_CONTEXT, ROUTE_RESOLVER_CONTEXT, Router, WILDCARD_MATCH, fillSegments, toSegments, useRoute, useRouteResolverContext } from "./router/index.js";
|
|
4
|
-
export { GROUP_MATCH, HOOK_MATCH, OPTIONAL_PARAMETER_MATCH, OPTIONAL_WILDCARD_MATCH, PARAMETER_MATCH, ROUTE_CONTEXT, ROUTE_RESOLVER_CONTEXT, Router, STATE_CONTEXT, VARIABLE_CONTEXT, WILDCARD_MATCH, createScope, defineContext, defineHook, definePlugin, dispatchHook, dispatchHookSingle, fillSegments, getContext, mimeType, mimeTypes, registerHook, registerHooks, setContext, toSegments, useRoute, useRouteResolverContext, useVariableContext, useVariables };
|
|
5
|
+
export { BASE64_DECODE, BASE64_ENCODE, ENCRYPTION_ALGORITHM, GROUP_MATCH, HOOK_MATCH, MIN_SECRET_LENGTH, OPTIONAL_PARAMETER_MATCH, OPTIONAL_WILDCARD_MATCH, PARAMETER_MATCH, PASSWORD_ALGORITHM, PASSWORD_ITERATIONS, ROUTE_CONTEXT, ROUTE_RESOLVER_CONTEXT, Router, STATE_CONTEXT, VARIABLE_CONTEXT, WILDCARD_MATCH, createScope, decoder, decrypt, defineContext, defineHook, definePlugin, derivePassword, digest, dispatchHook, dispatchHookSingle, encoder, encrypt, equalsConstantTime, fillSegments, getContext, hashPassword, isPasswordHash, mimeType, mimeTypes, needsPasswordRehash, parsePasswordHash, randomToken, registerHook, registerHooks, setContext, sign, toBytes, toSegments, useRoute, useRouteResolverContext, useSecretKey, useVariableContext, useVariables, verify, verifyPassword };
|
package/dist/module/index.js
CHANGED
package/dist/package.js
CHANGED