@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mybucks.online/core",
3
- "version": "2.2.0",
3
+ "version": "2.2.2",
4
4
  "description": "Core module of Mybucks.online Crypto Wallet",
5
5
  "main": "index.js",
6
6
  "type": "module",
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
- const base64Encoded = payloadBuffer.toString("base64");
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
- const decoded = Buffer.from(payload, "base64");
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;
@@ -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
  });