hanzio 1.0.7 → 1.0.9
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/README.md +8 -16
- package/dist/index.d.ts +0 -1
- package/dist/jwt/index.d.ts +3 -3
- package/dist/secrets/index.d.ts +1 -0
- package/dist/secrets/infisical.d.ts +2 -0
- package/dist/src/index.js +190 -47
- package/dist/src/jwt/index.js +98 -7
- package/dist/src/secrets/index.js +338 -26
- package/dist/src/secrets/infisical.js +339 -0
- package/package.json +22 -13
- package/src/index.ts +0 -1
- package/src/jwt/index.ts +158 -12
- package/src/secrets/index.ts +2 -43
- package/src/secrets/infisical.ts +108 -0
- package/src/gzip/index.ts +0 -29
package/README.md
CHANGED
|
@@ -2,17 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
A small TypeScript-first utility library for common application code: arrays,
|
|
4
4
|
strings, promises, caching, state machines, dates, URLs, Zod helpers, API clients,
|
|
5
|
-
JWTs,
|
|
5
|
+
JWTs, sitemap crawling, queues, secrets, and colorful terminal logging.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
bun add hanzio
|
|
10
|
+
bun add hanzio zod
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
The package is ESM and Bun-friendly. Bun consumers resolve the `bun` export to
|
|
14
14
|
the source TypeScript files, while Node/bundler consumers resolve built files
|
|
15
15
|
from `dist`.
|
|
16
|
+
`zod` is a peer dependency because the Zod helpers and API wrapper share the
|
|
17
|
+
consumer application's Zod instance.
|
|
16
18
|
|
|
17
19
|
## Imports
|
|
18
20
|
|
|
@@ -27,10 +29,10 @@ Heavier or dependency-specific modules are also available as subpath exports:
|
|
|
27
29
|
```ts
|
|
28
30
|
import { createZId } from 'hanzio/zod'
|
|
29
31
|
import { jwtSign } from 'hanzio/jwt'
|
|
30
|
-
import { compressString } from 'hanzio/gzip'
|
|
31
32
|
import { getDomainSitemap } from 'hanzio/sitemap'
|
|
32
33
|
import PQueue from 'hanzio/p-queue'
|
|
33
34
|
import { createCoolLogger } from 'hanzio/cool-console-log'
|
|
35
|
+
import { defineSecretSet } from 'hanzio/secrets'
|
|
34
36
|
```
|
|
35
37
|
|
|
36
38
|
The package currently publishes `src/**/*.ts` intentionally so Bun can consume
|
|
@@ -217,7 +219,7 @@ await promiseTimeout(250)
|
|
|
217
219
|
Infisical before the rest of the program starts.
|
|
218
220
|
|
|
219
221
|
```ts
|
|
220
|
-
import { defineSecretSet } from 'hanzio'
|
|
222
|
+
import { defineSecretSet } from 'hanzio/secrets'
|
|
221
223
|
|
|
222
224
|
export const backendSecrets = await defineSecretSet(
|
|
223
225
|
['DATABASE_URL', 'APP_SECRET'] as const,
|
|
@@ -229,7 +231,8 @@ const databaseUrl = backendSecrets.secret('DATABASE_URL')
|
|
|
229
231
|
|
|
230
232
|
For Vite config, `viteSecretSetPlugin(secretSet)` exposes the loaded values as
|
|
231
233
|
`import.meta.env.*` replacements. Use `getViteDefine(secretSet)` if you need the
|
|
232
|
-
raw `define` object.
|
|
234
|
+
raw `define` object. Import Vite helpers from `hanzio/secrets/vite` so browser
|
|
235
|
+
bundles do not pull in the Infisical SDK.
|
|
233
236
|
|
|
234
237
|
### Cache
|
|
235
238
|
|
|
@@ -470,17 +473,6 @@ const token = await jwtSign({ sub: 'user_1' }, 'secret')
|
|
|
470
473
|
const payload = await jwtVerify<{ sub: string }>(token, 'secret')
|
|
471
474
|
```
|
|
472
475
|
|
|
473
|
-
### Gzip
|
|
474
|
-
|
|
475
|
-
`compressString` and `decompressString` gzip roundtrip strings as base64.
|
|
476
|
-
|
|
477
|
-
```ts
|
|
478
|
-
import { compressString, decompressString } from 'hanzio/gzip'
|
|
479
|
-
|
|
480
|
-
const compressed = compressString('hello')
|
|
481
|
-
const text = decompressString(compressed)
|
|
482
|
-
```
|
|
483
|
-
|
|
484
476
|
### Sitemap
|
|
485
477
|
|
|
486
478
|
`getDomainSitemap` discovers and crawls sitemap URLs for a domain.
|
package/dist/index.d.ts
CHANGED
package/dist/jwt/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
export declare function jwtSign<
|
|
3
|
-
export declare function jwtVerify<Payload extends
|
|
1
|
+
export type JWTPayload = Record<string, unknown>;
|
|
2
|
+
export declare function jwtSign<Payload extends JWTPayload>(payload: Payload, secret: string): Promise<string>;
|
|
3
|
+
export declare function jwtVerify<Payload extends JWTPayload>(token: string, secret: string): Promise<Payload | null>;
|
package/dist/secrets/index.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export type SecretSet<SecretKey extends string> = {
|
|
|
22
22
|
secrets: () => Record<SecretKey, string>;
|
|
23
23
|
reload: () => Promise<SecretSet<SecretKey>>;
|
|
24
24
|
};
|
|
25
|
+
export * from './infisical';
|
|
25
26
|
export * from './vite';
|
|
26
27
|
export declare function getSecretEnvironment(): SecretEnvironment;
|
|
27
28
|
export declare function defineSecretSet<const SecretKeys extends readonly string[]>(keys: SecretKeys, options: DefineSecretSetOptions<SecretKeys[number]>): Promise<SecretSet<SecretKeys[number]>>;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import type { SecretSetLoaderContext } from '.';
|
|
2
|
+
export declare function loadInfisicalSecrets<SecretKey extends string>({ keys, projectId, environment, siteUrl, clientIdEnvKey, clientSecretEnvKey }: SecretSetLoaderContext<SecretKey>): Promise<Partial<Record<SecretKey, string>>>;
|
package/dist/src/index.js
CHANGED
|
@@ -880,32 +880,110 @@ class PQueue {
|
|
|
880
880
|
}
|
|
881
881
|
|
|
882
882
|
// src/jwt/index.ts
|
|
883
|
-
|
|
883
|
+
var BYTE_CHUNK_SIZE = 32768;
|
|
884
|
+
var textEncoder = new TextEncoder;
|
|
885
|
+
var textDecoder = new TextDecoder;
|
|
884
886
|
async function jwtSign(payload, secret) {
|
|
885
|
-
const
|
|
886
|
-
|
|
887
|
+
const header = encodeJson({ alg: "HS256" });
|
|
888
|
+
const body = encodeJson({
|
|
889
|
+
...payload,
|
|
890
|
+
iat: Math.floor(Date.now() / 1000)
|
|
891
|
+
});
|
|
892
|
+
const data = `${header}.${body}`;
|
|
893
|
+
const signature = await sign(data, secret);
|
|
894
|
+
return `${data}.${encodeBase64Url(signature)}`;
|
|
887
895
|
}
|
|
888
|
-
async function
|
|
896
|
+
async function jwtVerify(token, secret) {
|
|
889
897
|
try {
|
|
890
|
-
const
|
|
891
|
-
|
|
898
|
+
const parts = token.split(".");
|
|
899
|
+
if (parts.length !== 3)
|
|
900
|
+
return null;
|
|
901
|
+
const [encodedHeader, encodedPayload, encodedSignature] = parts;
|
|
902
|
+
const header = decodeJson(encodedHeader);
|
|
903
|
+
if (!isJwtHeader(header))
|
|
904
|
+
return null;
|
|
905
|
+
const data = `${encodedHeader}.${encodedPayload}`;
|
|
906
|
+
const signature = decodeBase64Url(encodedSignature);
|
|
907
|
+
const isValid = await verify(data, signature, secret);
|
|
908
|
+
if (!isValid)
|
|
909
|
+
return null;
|
|
910
|
+
const payload = decodeJson(encodedPayload);
|
|
911
|
+
if (!isJwtPayload(payload) || !isTimeValid(payload))
|
|
912
|
+
return null;
|
|
892
913
|
return payload;
|
|
893
914
|
} catch (_error) {
|
|
894
915
|
return null;
|
|
895
916
|
}
|
|
896
917
|
}
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
const
|
|
903
|
-
return
|
|
904
|
-
}
|
|
905
|
-
function
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
918
|
+
async function sign(data, secret) {
|
|
919
|
+
const key = await getKey(secret, ["sign"]);
|
|
920
|
+
return await getSubtleCrypto().sign("HMAC", key, encodeText(data));
|
|
921
|
+
}
|
|
922
|
+
async function verify(data, signature, secret) {
|
|
923
|
+
const key = await getKey(secret, ["verify"]);
|
|
924
|
+
return await getSubtleCrypto().verify("HMAC", key, signature, encodeText(data));
|
|
925
|
+
}
|
|
926
|
+
async function getKey(secret, usages) {
|
|
927
|
+
return await getSubtleCrypto().importKey("raw", encodeText(secret), {
|
|
928
|
+
name: "HMAC",
|
|
929
|
+
hash: "SHA-256"
|
|
930
|
+
}, false, usages);
|
|
931
|
+
}
|
|
932
|
+
function encodeJson(value) {
|
|
933
|
+
return encodeBase64Url(encodeText(JSON.stringify(value)));
|
|
934
|
+
}
|
|
935
|
+
function decodeJson(value) {
|
|
936
|
+
return JSON.parse(textDecoder.decode(decodeBase64Url(value)));
|
|
937
|
+
}
|
|
938
|
+
function encodeBase64Url(value) {
|
|
939
|
+
const bytes = value instanceof ArrayBuffer ? new Uint8Array(value) : value;
|
|
940
|
+
return btoa(bytesToBinaryString(bytes)).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
941
|
+
}
|
|
942
|
+
function decodeBase64Url(value) {
|
|
943
|
+
const base64 = value.replaceAll("-", "+").replaceAll("_", "/");
|
|
944
|
+
const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, "=");
|
|
945
|
+
return binaryStringToBytes(atob(padded)).buffer;
|
|
946
|
+
}
|
|
947
|
+
function encodeText(value) {
|
|
948
|
+
return toArrayBuffer(textEncoder.encode(value));
|
|
949
|
+
}
|
|
950
|
+
function toArrayBuffer(bytes) {
|
|
951
|
+
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
952
|
+
}
|
|
953
|
+
function bytesToBinaryString(bytes) {
|
|
954
|
+
let binary = "";
|
|
955
|
+
for (let offset = 0;offset < bytes.length; offset += BYTE_CHUNK_SIZE) {
|
|
956
|
+
binary += String.fromCharCode(...bytes.subarray(offset, offset + BYTE_CHUNK_SIZE));
|
|
957
|
+
}
|
|
958
|
+
return binary;
|
|
959
|
+
}
|
|
960
|
+
function binaryStringToBytes(binary) {
|
|
961
|
+
const bytes = new Uint8Array(binary.length);
|
|
962
|
+
for (let index = 0;index < binary.length; index += 1) {
|
|
963
|
+
bytes[index] = binary.charCodeAt(index);
|
|
964
|
+
}
|
|
965
|
+
return bytes;
|
|
966
|
+
}
|
|
967
|
+
function getSubtleCrypto() {
|
|
968
|
+
const subtle = globalThis.crypto?.subtle;
|
|
969
|
+
if (!subtle) {
|
|
970
|
+
throw new Error("Web Crypto is not available in this runtime");
|
|
971
|
+
}
|
|
972
|
+
return subtle;
|
|
973
|
+
}
|
|
974
|
+
function isJwtHeader(value) {
|
|
975
|
+
return typeof value === "object" && value !== null && "alg" in value && value.alg === "HS256";
|
|
976
|
+
}
|
|
977
|
+
function isJwtPayload(value) {
|
|
978
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
979
|
+
}
|
|
980
|
+
function isTimeValid(payload) {
|
|
981
|
+
const now = Math.floor(Date.now() / 1000);
|
|
982
|
+
if (typeof payload.exp === "number" && now >= payload.exp)
|
|
983
|
+
return false;
|
|
984
|
+
if (typeof payload.nbf === "number" && now < payload.nbf)
|
|
985
|
+
return false;
|
|
986
|
+
return true;
|
|
909
987
|
}
|
|
910
988
|
|
|
911
989
|
// src/sitemap/index.ts
|
|
@@ -1023,6 +1101,98 @@ var getDomainSitemap = async (domain, options = {}) => {
|
|
|
1023
1101
|
return [...result.links];
|
|
1024
1102
|
};
|
|
1025
1103
|
|
|
1104
|
+
// src/secrets/infisical.ts
|
|
1105
|
+
import { z as z4 } from "zod";
|
|
1106
|
+
var defaultFetch = globalThis.fetch;
|
|
1107
|
+
var infisicalAuthResponseSchema = z4.object({
|
|
1108
|
+
accessToken: z4.string()
|
|
1109
|
+
});
|
|
1110
|
+
var infisicalSecretSchema = z4.object({
|
|
1111
|
+
secretKey: z4.string(),
|
|
1112
|
+
secretValue: z4.string()
|
|
1113
|
+
});
|
|
1114
|
+
var infisicalSecretsResponseSchema = z4.object({
|
|
1115
|
+
secrets: z4.array(infisicalSecretSchema)
|
|
1116
|
+
});
|
|
1117
|
+
async function loadInfisicalSecrets({
|
|
1118
|
+
keys,
|
|
1119
|
+
projectId,
|
|
1120
|
+
environment,
|
|
1121
|
+
siteUrl,
|
|
1122
|
+
clientIdEnvKey,
|
|
1123
|
+
clientSecretEnvKey
|
|
1124
|
+
}) {
|
|
1125
|
+
const clientId = process.env[clientIdEnvKey];
|
|
1126
|
+
const clientSecret = process.env[clientSecretEnvKey];
|
|
1127
|
+
if (!clientId || !clientSecret) {
|
|
1128
|
+
throw new Error("Missing Infisical credentials");
|
|
1129
|
+
}
|
|
1130
|
+
const infisical = createInfisicalApiClient(siteUrl);
|
|
1131
|
+
const { data: auth } = await infisical.request("login", {
|
|
1132
|
+
reqBody: {
|
|
1133
|
+
clientId,
|
|
1134
|
+
clientSecret
|
|
1135
|
+
}
|
|
1136
|
+
});
|
|
1137
|
+
const { data } = await infisical.request("listSecrets", {
|
|
1138
|
+
reqHeaders: {
|
|
1139
|
+
Authorization: `Bearer ${auth.accessToken}`
|
|
1140
|
+
},
|
|
1141
|
+
reqQuery: {
|
|
1142
|
+
environment,
|
|
1143
|
+
projectId,
|
|
1144
|
+
secretPath: "/",
|
|
1145
|
+
viewSecretValue: true,
|
|
1146
|
+
expandSecretReferences: true,
|
|
1147
|
+
includeImports: true
|
|
1148
|
+
}
|
|
1149
|
+
});
|
|
1150
|
+
const wantedKeys = new Set(keys);
|
|
1151
|
+
return Object.fromEntries(data.secrets.filter((secret) => wantedKeys.has(secret.secretKey)).map((secret) => [secret.secretKey, secret.secretValue]));
|
|
1152
|
+
}
|
|
1153
|
+
function createInfisicalApiClient(siteUrl) {
|
|
1154
|
+
return createApiClient({
|
|
1155
|
+
name: "infisical",
|
|
1156
|
+
baseApiUrls: {
|
|
1157
|
+
infisical: siteUrl
|
|
1158
|
+
},
|
|
1159
|
+
defaultBaseApiUrl: "infisical",
|
|
1160
|
+
fetch: getFetch(),
|
|
1161
|
+
retries: 1,
|
|
1162
|
+
endpoints: {
|
|
1163
|
+
login: {
|
|
1164
|
+
method: "POST",
|
|
1165
|
+
path: "/api/v1/auth/universal-auth/login",
|
|
1166
|
+
reqBodySchema: z4.object({
|
|
1167
|
+
clientId: z4.string(),
|
|
1168
|
+
clientSecret: z4.string()
|
|
1169
|
+
}),
|
|
1170
|
+
resSchema: infisicalAuthResponseSchema,
|
|
1171
|
+
reqBodyFormat: "json"
|
|
1172
|
+
},
|
|
1173
|
+
listSecrets: {
|
|
1174
|
+
method: "GET",
|
|
1175
|
+
path: "/api/v4/secrets",
|
|
1176
|
+
reqHeadersSchema: z4.object({
|
|
1177
|
+
Authorization: z4.string()
|
|
1178
|
+
}),
|
|
1179
|
+
reqQuerySchema: z4.object({
|
|
1180
|
+
projectId: z4.string(),
|
|
1181
|
+
environment: z4.string(),
|
|
1182
|
+
secretPath: z4.string(),
|
|
1183
|
+
viewSecretValue: z4.boolean(),
|
|
1184
|
+
expandSecretReferences: z4.boolean(),
|
|
1185
|
+
includeImports: z4.boolean()
|
|
1186
|
+
}),
|
|
1187
|
+
resSchema: infisicalSecretsResponseSchema
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
function getFetch() {
|
|
1193
|
+
return globalThis.fetch ?? defaultFetch;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1026
1196
|
// src/secrets/vite.ts
|
|
1027
1197
|
function getViteDefine(secretSet) {
|
|
1028
1198
|
return Object.fromEntries(Object.entries(secretSet.secrets()).map(([key, value]) => [
|
|
@@ -1040,7 +1210,6 @@ function viteSecretSetPlugin(secretSet) {
|
|
|
1040
1210
|
}
|
|
1041
1211
|
|
|
1042
1212
|
// src/secrets/index.ts
|
|
1043
|
-
import { InfisicalSDK } from "@infisical/sdk";
|
|
1044
1213
|
import { networkInterfaces } from "node:os";
|
|
1045
1214
|
|
|
1046
1215
|
// src/cool-console-log/index.ts
|
|
@@ -1220,31 +1389,6 @@ async function defineSecretSet(keys, options) {
|
|
|
1220
1389
|
await loadSecrets();
|
|
1221
1390
|
return secretSet;
|
|
1222
1391
|
}
|
|
1223
|
-
async function loadInfisicalSecrets({
|
|
1224
|
-
keys,
|
|
1225
|
-
projectId,
|
|
1226
|
-
environment,
|
|
1227
|
-
siteUrl,
|
|
1228
|
-
clientIdEnvKey,
|
|
1229
|
-
clientSecretEnvKey
|
|
1230
|
-
}) {
|
|
1231
|
-
const clientId = process.env[clientIdEnvKey];
|
|
1232
|
-
const clientSecret = process.env[clientSecretEnvKey];
|
|
1233
|
-
if (!clientId || !clientSecret) {
|
|
1234
|
-
throw new Error("Missing Infisical credentials");
|
|
1235
|
-
}
|
|
1236
|
-
const client = new InfisicalSDK({ siteUrl });
|
|
1237
|
-
await client.auth().universalAuth.login({
|
|
1238
|
-
clientId,
|
|
1239
|
-
clientSecret
|
|
1240
|
-
});
|
|
1241
|
-
const { secrets } = await client.secrets().listSecrets({
|
|
1242
|
-
environment,
|
|
1243
|
-
projectId
|
|
1244
|
-
});
|
|
1245
|
-
const wantedKeys = new Set(keys);
|
|
1246
|
-
return Object.fromEntries(secrets.filter((secret) => wantedKeys.has(secret.secretKey)).map((secret) => [secret.secretKey, secret.secretValue]));
|
|
1247
|
-
}
|
|
1248
1392
|
function resolveEnvironment(environment) {
|
|
1249
1393
|
if (typeof environment === "function")
|
|
1250
1394
|
return environment();
|
|
@@ -1935,9 +2079,10 @@ export {
|
|
|
1935
2079
|
normalizeSingleOrArray,
|
|
1936
2080
|
logOperationSummary,
|
|
1937
2081
|
logBanner,
|
|
2082
|
+
loadInfisicalSecrets,
|
|
1938
2083
|
linearRegressionTrend,
|
|
1939
2084
|
keyBy,
|
|
1940
|
-
|
|
2085
|
+
jwtVerify,
|
|
1941
2086
|
jwtSign,
|
|
1942
2087
|
jsonToZodSchemaMapper,
|
|
1943
2088
|
isSuccess,
|
|
@@ -1957,13 +2102,11 @@ export {
|
|
|
1957
2102
|
findArrayDifferenceByKey,
|
|
1958
2103
|
extractNumber,
|
|
1959
2104
|
defineSecretSet,
|
|
1960
|
-
decompressString,
|
|
1961
2105
|
createZId,
|
|
1962
2106
|
createResolveColorsByKeys,
|
|
1963
2107
|
createCoolLogger,
|
|
1964
2108
|
createApiWrapper,
|
|
1965
2109
|
createApiClient,
|
|
1966
|
-
compressString,
|
|
1967
2110
|
compact,
|
|
1968
2111
|
colorize,
|
|
1969
2112
|
chunkArray,
|
package/dist/src/jwt/index.js
CHANGED
|
@@ -1,19 +1,110 @@
|
|
|
1
1
|
// src/jwt/index.ts
|
|
2
|
-
|
|
2
|
+
var BYTE_CHUNK_SIZE = 32768;
|
|
3
|
+
var textEncoder = new TextEncoder;
|
|
4
|
+
var textDecoder = new TextDecoder;
|
|
3
5
|
async function jwtSign(payload, secret) {
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
+
const header = encodeJson({ alg: "HS256" });
|
|
7
|
+
const body = encodeJson({
|
|
8
|
+
...payload,
|
|
9
|
+
iat: Math.floor(Date.now() / 1000)
|
|
10
|
+
});
|
|
11
|
+
const data = `${header}.${body}`;
|
|
12
|
+
const signature = await sign(data, secret);
|
|
13
|
+
return `${data}.${encodeBase64Url(signature)}`;
|
|
6
14
|
}
|
|
7
|
-
async function
|
|
15
|
+
async function jwtVerify(token, secret) {
|
|
8
16
|
try {
|
|
9
|
-
const
|
|
10
|
-
|
|
17
|
+
const parts = token.split(".");
|
|
18
|
+
if (parts.length !== 3)
|
|
19
|
+
return null;
|
|
20
|
+
const [encodedHeader, encodedPayload, encodedSignature] = parts;
|
|
21
|
+
const header = decodeJson(encodedHeader);
|
|
22
|
+
if (!isJwtHeader(header))
|
|
23
|
+
return null;
|
|
24
|
+
const data = `${encodedHeader}.${encodedPayload}`;
|
|
25
|
+
const signature = decodeBase64Url(encodedSignature);
|
|
26
|
+
const isValid = await verify(data, signature, secret);
|
|
27
|
+
if (!isValid)
|
|
28
|
+
return null;
|
|
29
|
+
const payload = decodeJson(encodedPayload);
|
|
30
|
+
if (!isJwtPayload(payload) || !isTimeValid(payload))
|
|
31
|
+
return null;
|
|
11
32
|
return payload;
|
|
12
33
|
} catch (_error) {
|
|
13
34
|
return null;
|
|
14
35
|
}
|
|
15
36
|
}
|
|
37
|
+
async function sign(data, secret) {
|
|
38
|
+
const key = await getKey(secret, ["sign"]);
|
|
39
|
+
return await getSubtleCrypto().sign("HMAC", key, encodeText(data));
|
|
40
|
+
}
|
|
41
|
+
async function verify(data, signature, secret) {
|
|
42
|
+
const key = await getKey(secret, ["verify"]);
|
|
43
|
+
return await getSubtleCrypto().verify("HMAC", key, signature, encodeText(data));
|
|
44
|
+
}
|
|
45
|
+
async function getKey(secret, usages) {
|
|
46
|
+
return await getSubtleCrypto().importKey("raw", encodeText(secret), {
|
|
47
|
+
name: "HMAC",
|
|
48
|
+
hash: "SHA-256"
|
|
49
|
+
}, false, usages);
|
|
50
|
+
}
|
|
51
|
+
function encodeJson(value) {
|
|
52
|
+
return encodeBase64Url(encodeText(JSON.stringify(value)));
|
|
53
|
+
}
|
|
54
|
+
function decodeJson(value) {
|
|
55
|
+
return JSON.parse(textDecoder.decode(decodeBase64Url(value)));
|
|
56
|
+
}
|
|
57
|
+
function encodeBase64Url(value) {
|
|
58
|
+
const bytes = value instanceof ArrayBuffer ? new Uint8Array(value) : value;
|
|
59
|
+
return btoa(bytesToBinaryString(bytes)).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
60
|
+
}
|
|
61
|
+
function decodeBase64Url(value) {
|
|
62
|
+
const base64 = value.replaceAll("-", "+").replaceAll("_", "/");
|
|
63
|
+
const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, "=");
|
|
64
|
+
return binaryStringToBytes(atob(padded)).buffer;
|
|
65
|
+
}
|
|
66
|
+
function encodeText(value) {
|
|
67
|
+
return toArrayBuffer(textEncoder.encode(value));
|
|
68
|
+
}
|
|
69
|
+
function toArrayBuffer(bytes) {
|
|
70
|
+
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
71
|
+
}
|
|
72
|
+
function bytesToBinaryString(bytes) {
|
|
73
|
+
let binary = "";
|
|
74
|
+
for (let offset = 0;offset < bytes.length; offset += BYTE_CHUNK_SIZE) {
|
|
75
|
+
binary += String.fromCharCode(...bytes.subarray(offset, offset + BYTE_CHUNK_SIZE));
|
|
76
|
+
}
|
|
77
|
+
return binary;
|
|
78
|
+
}
|
|
79
|
+
function binaryStringToBytes(binary) {
|
|
80
|
+
const bytes = new Uint8Array(binary.length);
|
|
81
|
+
for (let index = 0;index < binary.length; index += 1) {
|
|
82
|
+
bytes[index] = binary.charCodeAt(index);
|
|
83
|
+
}
|
|
84
|
+
return bytes;
|
|
85
|
+
}
|
|
86
|
+
function getSubtleCrypto() {
|
|
87
|
+
const subtle = globalThis.crypto?.subtle;
|
|
88
|
+
if (!subtle) {
|
|
89
|
+
throw new Error("Web Crypto is not available in this runtime");
|
|
90
|
+
}
|
|
91
|
+
return subtle;
|
|
92
|
+
}
|
|
93
|
+
function isJwtHeader(value) {
|
|
94
|
+
return typeof value === "object" && value !== null && "alg" in value && value.alg === "HS256";
|
|
95
|
+
}
|
|
96
|
+
function isJwtPayload(value) {
|
|
97
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
98
|
+
}
|
|
99
|
+
function isTimeValid(payload) {
|
|
100
|
+
const now = Math.floor(Date.now() / 1000);
|
|
101
|
+
if (typeof payload.exp === "number" && now >= payload.exp)
|
|
102
|
+
return false;
|
|
103
|
+
if (typeof payload.nbf === "number" && now < payload.nbf)
|
|
104
|
+
return false;
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
16
107
|
export {
|
|
17
|
-
|
|
108
|
+
jwtVerify,
|
|
18
109
|
jwtSign
|
|
19
110
|
};
|