@mybucks.online/core 2.2.0 → 2.2.2
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/package.json +1 -1
- package/src/random.js +12 -1
- package/src/token.js +15 -2
- package/test/index.test.js +55 -0
package/package.json
CHANGED
package/src/random.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { randomInt } from "crypto";
|
|
2
1
|
import zxcvbn from "zxcvbn";
|
|
3
2
|
import {
|
|
4
3
|
PASSPHRASE_MIN_ZXCVBN_SCORE,
|
|
@@ -10,6 +9,18 @@ const LOWER = "abcdefghijklmnopqrstuvwxyz";
|
|
|
10
9
|
const DIGITS = "0123456789";
|
|
11
10
|
const SYMBOLS = "`~!@#$%^&*()-_+={}[]\\|:;\"'<>,.?/";
|
|
12
11
|
|
|
12
|
+
function randomInt(max) {
|
|
13
|
+
if (max > 255) {
|
|
14
|
+
throw new RangeError(`max must be <= 255, got ${max}`);
|
|
15
|
+
}
|
|
16
|
+
const arr = new Uint8Array(1);
|
|
17
|
+
const limit = 256 - (256 % max);
|
|
18
|
+
do {
|
|
19
|
+
globalThis.crypto.getRandomValues(arr);
|
|
20
|
+
} while (arr[0] >= limit);
|
|
21
|
+
return arr[0] % max;
|
|
22
|
+
}
|
|
23
|
+
|
|
13
24
|
function randomChar(charset) {
|
|
14
25
|
return charset[randomInt(charset.length)];
|
|
15
26
|
}
|
package/src/token.js
CHANGED
|
@@ -87,7 +87,12 @@ export function generateToken(passphrase, pin, network, legacy = false) {
|
|
|
87
87
|
]);
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
// Convert Base64 to Base64URL so token remains safe in URL hash/query contexts.
|
|
91
|
+
const base64Encoded = payloadBuffer
|
|
92
|
+
.toString("base64")
|
|
93
|
+
.replace(/\+/g, "-")
|
|
94
|
+
.replace(/\//g, "_")
|
|
95
|
+
.replace(/=+$/g, "");
|
|
91
96
|
const padding = nanoid(12);
|
|
92
97
|
return padding.slice(0, 6) + base64Encoded + padding.slice(6);
|
|
93
98
|
}
|
|
@@ -100,7 +105,15 @@ export function generateToken(passphrase, pin, network, legacy = false) {
|
|
|
100
105
|
*/
|
|
101
106
|
export function parseToken(token) {
|
|
102
107
|
const payload = token.slice(6, token.length - 6);
|
|
103
|
-
|
|
108
|
+
// Normalize payload for robust URL transport:
|
|
109
|
+
// - URLSearchParams converts "+" to " "
|
|
110
|
+
// - base64url may use "-" and "_" and omit padding
|
|
111
|
+
const normalized = payload
|
|
112
|
+
.replace(/ /g, "+")
|
|
113
|
+
.replace(/-/g, "+")
|
|
114
|
+
.replace(/_/g, "/");
|
|
115
|
+
const padded = normalized + "=".repeat((4 - (normalized.length % 4)) % 4);
|
|
116
|
+
const decoded = Buffer.from(padded, "base64");
|
|
104
117
|
|
|
105
118
|
if (decoded[0] === TOKEN_VERSION_COMPACT) {
|
|
106
119
|
let i = 1;
|
package/test/index.test.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
generateToken,
|
|
9
9
|
parseToken,
|
|
10
10
|
} from "../index.js";
|
|
11
|
+
import { randomPassphrase, randomPIN } from "../src/random.js";
|
|
11
12
|
|
|
12
13
|
const DEMO_PASSPHRASE = "DemoAccount5&";
|
|
13
14
|
const DEMO_PIN = "112324";
|
|
@@ -326,6 +327,40 @@ describe("parseToken", () => {
|
|
|
326
327
|
assert.strictEqual(network, DEMO_NETWORK);
|
|
327
328
|
assert.strictEqual(legacy, false);
|
|
328
329
|
});
|
|
330
|
+
|
|
331
|
+
test("should parse token after hash-fragment character normalization", () => {
|
|
332
|
+
const plusToken =
|
|
333
|
+
"tmeNhvAhsxe1lhSWwtJyhZLkQ+LUk2dzZeLi1TI0FPQzwIMWNuZ3I0ZTkHcG9seWdvbg==GB_ha6";
|
|
334
|
+
const spaceToken =
|
|
335
|
+
"tmeNhvAhsxe1lhSWwtJyhZLkQ LUk2dzZeLi1TI0FPQzwIMWNuZ3I0ZTkHcG9seWdvbg==GB_ha6";
|
|
336
|
+
assert.deepStrictEqual(
|
|
337
|
+
parseToken(spaceToken),
|
|
338
|
+
parseToken(plusToken),
|
|
339
|
+
"space should be treated as '+'"
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
const plusToken2 =
|
|
343
|
+
"gUerR9AhtTbmhANnYtST08PnFRLUMsZyUxMy1+OHc8byIINXJ6cG5oamkHcG9seWdvbg==2lSvB2";
|
|
344
|
+
const dashToken2 =
|
|
345
|
+
"gUerR9AhtTbmhANnYtST08PnFRLUMsZyUxMy1-OHc8byIINXJ6cG5oamkHcG9seWdvbg==2lSvB2";
|
|
346
|
+
assert.deepStrictEqual(
|
|
347
|
+
parseToken(dashToken2),
|
|
348
|
+
parseToken(plusToken2),
|
|
349
|
+
"'-' should be treated as '+' in payload normalization"
|
|
350
|
+
);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test("should parse token when base64 padding is stripped", () => {
|
|
354
|
+
const tokenWithPadding =
|
|
355
|
+
"tmeNhvAhsxe1lhSWwtJyhZLkQ+LUk2dzZeLi1TI0FPQzwIMWNuZ3I0ZTkHcG9seWdvbg==GB_ha6";
|
|
356
|
+
const tokenWithoutPadding =
|
|
357
|
+
"tmeNhvAhsxe1lhSWwtJyhZLkQ+LUk2dzZeLi1TI0FPQzwIMWNuZ3I0ZTkHcG9seWdvbgGB_ha6";
|
|
358
|
+
assert.deepStrictEqual(
|
|
359
|
+
parseToken(tokenWithoutPadding),
|
|
360
|
+
parseToken(tokenWithPadding),
|
|
361
|
+
"missing '=' padding should be recovered"
|
|
362
|
+
);
|
|
363
|
+
});
|
|
329
364
|
});
|
|
330
365
|
|
|
331
366
|
describe("generateToken and parseToken", () => {
|
|
@@ -382,4 +417,24 @@ describe("generateToken and parseToken", () => {
|
|
|
382
417
|
);
|
|
383
418
|
assert.strictEqual(legacy, true);
|
|
384
419
|
});
|
|
420
|
+
|
|
421
|
+
test("should round-trip random passphrase and PIN for 100 cases (integration)", () => {
|
|
422
|
+
const testNetwork = "polygon";
|
|
423
|
+
|
|
424
|
+
for (let i = 0; i < 100; i++) {
|
|
425
|
+
const testPassphrase = randomPassphrase();
|
|
426
|
+
const testPin = randomPIN();
|
|
427
|
+
const token = generateToken(testPassphrase, testPin, testNetwork, false);
|
|
428
|
+
|
|
429
|
+
const [passphrase, pin, network, legacy] = parseToken(token);
|
|
430
|
+
assert.strictEqual(
|
|
431
|
+
passphrase,
|
|
432
|
+
testPassphrase,
|
|
433
|
+
`passphrase mismatch at case ${i}`,
|
|
434
|
+
);
|
|
435
|
+
assert.strictEqual(pin, testPin, `pin mismatch at case ${i}`);
|
|
436
|
+
assert.strictEqual(network, testNetwork, `network mismatch at case ${i}`);
|
|
437
|
+
assert.strictEqual(legacy, false, `legacy flag mismatch at case ${i}`);
|
|
438
|
+
}
|
|
439
|
+
});
|
|
385
440
|
});
|