@oneaddress/setup 1.3.1 → 1.3.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.
Files changed (2) hide show
  1. package/dist/index.js +85 -22
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -856,7 +856,7 @@ var _R = ["\u2588\u2588\u2588\u2588\u2588\u2588 ", "\u2588\u2588 \u2588\u2588"
856
856
  var _S = [" \u2588\u2588\u2588\u2588\u2588\u2588", "\u2588\u2588 ", "\u2588\u2588 ", " \u2588\u2588\u2588\u2588\u2588 ", " \u2588\u2588", " \u2588\u2588", "\u2588\u2588\u2588\u2588\u2588\u2588 "];
857
857
  var ONE_ROWS = Array.from({ length: 7 }, (_3, i) => [_O[i], _N[i], _E[i]].join(" "));
858
858
  var ADDR_ROWS = Array.from({ length: 7 }, (_3, i) => [_A[i], _D2[i], _D2[i], _R[i], _E[i], _S[i], _S[i]].join(" "));
859
- var WIZARD_VERSION = true ? "1.3.1" : "?";
859
+ var WIZARD_VERSION = true ? "1.3.2" : "?";
860
860
  function printCompactHeader() {
861
861
  const INNER = 42;
862
862
  const TOP = fn("\u250C") + dm("\u2500".repeat(INNER)) + fn("\u2510");
@@ -5224,7 +5224,7 @@ async function scaffold(platform, outputDir, partnerId, webhookSecret, webhookUr
5224
5224
 
5225
5225
  // src/register.ts
5226
5226
  var import_node_crypto = require("crypto");
5227
- var PKG_VERSION = true ? "1.3.1" : "dev";
5227
+ var PKG_VERSION = true ? "1.3.2" : "dev";
5228
5228
  var REGISTER_URL = "https://partners.oneaddress.io/api/partner/installs";
5229
5229
  function hmacSha256(secret, message) {
5230
5230
  return (0, import_node_crypto.createHmac)("sha256", secret).update(message).digest("hex");
@@ -5648,7 +5648,7 @@ async function resolveBinary() {
5648
5648
  return downloadCloudflared();
5649
5649
  }
5650
5650
  async function waitForTunnelReady(url, opts = {}) {
5651
- const budgetMs = opts.budgetMs ?? 3e4;
5651
+ const budgetMs = opts.budgetMs ?? 6e4;
5652
5652
  const intervalMs = opts.intervalMs ?? 1500;
5653
5653
  const attemptTimeoutMs = opts.attemptTimeoutMs ?? 4e3;
5654
5654
  const doFetch = opts.fetchImpl ?? fetch;
@@ -5766,6 +5766,26 @@ async function getVerifiesAccountReference(partnerId, webhookSecret, onError) {
5766
5766
  return void 0;
5767
5767
  }
5768
5768
  }
5769
+ async function verifyWebhookSecret(partnerId, webhookSecret) {
5770
+ try {
5771
+ const ts = String(Math.floor(Date.now() / 1e3));
5772
+ const sig = (0, import_node_crypto4.createHmac)("sha256", webhookSecret).update(`${ts}.`).digest("hex");
5773
+ const res = await fetch(PORTAL_API, {
5774
+ method: "GET",
5775
+ headers: {
5776
+ "X-OneAddress-Timestamp": ts,
5777
+ "X-OneAddress-Signature": sig,
5778
+ "X-OneAddress-Partner": partnerId
5779
+ },
5780
+ signal: AbortSignal.timeout(8e3)
5781
+ });
5782
+ if (res.ok) return "ok";
5783
+ if (res.status === 401 || res.status === 403) return "bad_secret";
5784
+ return "unreachable";
5785
+ } catch {
5786
+ return "unreachable";
5787
+ }
5788
+ }
5769
5789
  async function registerWebhookUrl(partnerId, webhookSecret, webhookUrl) {
5770
5790
  try {
5771
5791
  const body = JSON.stringify({ webhook_url: webhookUrl });
@@ -6033,14 +6053,42 @@ async function main() {
6033
6053
  M2.info(" 2. Scaffold a working webhook server for your platform");
6034
6054
  M2.info(" 3. Get it live with a passing conformance check \u2014 in under 5 minutes");
6035
6055
  M2.info("\nOpen partners.oneaddress.io \u2192 My Profile to find the two values below.\n");
6036
- const webhookSecret = await ge({
6037
- message: "Your Webhook signing secret (min 20 chars)",
6038
- validate: (v2) => {
6039
- if (!v2.trim()) return "Webhook signing secret is required";
6040
- if (v2.length < 20) return "That looks too short \u2014 paste the full secret from My Profile";
6056
+ let secret;
6057
+ for (let attempt = 1; ; attempt++) {
6058
+ const webhookSecret = await ge({
6059
+ message: attempt === 1 ? "Your Webhook signing secret (min 20 chars)" : `Your Webhook signing secret (attempt ${attempt})`,
6060
+ validate: (v2) => {
6061
+ if (!v2.trim()) return "Webhook signing secret is required";
6062
+ if (v2.length < 20) return "That looks too short \u2014 paste the full secret from My Profile";
6063
+ }
6064
+ });
6065
+ assertNotCancelled(webhookSecret);
6066
+ secret = webhookSecret.trim();
6067
+ const sv = Y2();
6068
+ sv.start("Checking your Webhook signing secret");
6069
+ const check = await verifyWebhookSecret(pid, secret);
6070
+ if (check === "ok") {
6071
+ sv.stop("Webhook signing secret verified");
6072
+ break;
6041
6073
  }
6042
- });
6043
- assertNotCancelled(webhookSecret);
6074
+ if (check === "unreachable") {
6075
+ sv.stop("Could not verify the secret against the portal, continuing");
6076
+ M2.warn(
6077
+ "Could not reach the portal to check your Webhook signing secret. Continuing.\nIf live dispatches later fail signature verification, re-check it at\npartners.oneaddress.io -> Webhook -> Webhook signing secret."
6078
+ );
6079
+ break;
6080
+ }
6081
+ sv.stop("That Webhook signing secret was rejected");
6082
+ if (attempt >= 3) {
6083
+ xe(
6084
+ "The Webhook signing secret did not match after three tries.\nRegenerate it at partners.oneaddress.io -> Webhook -> Webhook signing secret\n(use Revoke and regenerate, not Rotate), then run the wizard again."
6085
+ );
6086
+ process.exit(1);
6087
+ }
6088
+ M2.error(
6089
+ "The portal rejected that secret (HTTP 401): it does not match the one on your profile.\nRegenerate it at partners.oneaddress.io -> Webhook -> Webhook signing secret\n(use Revoke and regenerate, not Rotate), copy the value shown once, and paste it here."
6090
+ );
6091
+ }
6044
6092
  const privateKeyRaw = await he({
6045
6093
  message: "Your ECDH Private Key",
6046
6094
  placeholder: "Paste the base64 key body from My Profile, or enter a path to the .pem file",
@@ -6052,7 +6100,6 @@ async function main() {
6052
6100
  }
6053
6101
  });
6054
6102
  assertNotCancelled(privateKeyRaw);
6055
- const secret = webhookSecret.trim();
6056
6103
  const normalised = normalisePrivateKey(privateKeyRaw.trim());
6057
6104
  if (normalised.error) {
6058
6105
  xe(`Private key parse failed unexpectedly: ${normalised.error}`);
@@ -6177,6 +6224,8 @@ async function main() {
6177
6224
  if (start.output) M2.warn(start.output.slice(0, 600));
6178
6225
  }
6179
6226
  let webhookUrl = "";
6227
+ let webhookReady = true;
6228
+ let tunnelBase = null;
6180
6229
  let existingUrlReason = "";
6181
6230
  const existingUrlResult = await getExistingWebhookUrl(pid, secret, (r2) => {
6182
6231
  existingUrlReason = r2;
@@ -6237,6 +6286,7 @@ Continuing will overwrite it.`
6237
6286
  try {
6238
6287
  const tunnel = await startTunnel(3001);
6239
6288
  webhookUrl = `${tunnel.url}/webhook`;
6289
+ tunnelBase = tunnel.url;
6240
6290
  s4.stop(`Tunnel active: ${tunnel.url}`);
6241
6291
  M2.warn(
6242
6292
  "IMPORTANT: This tunnel URL is temporary \u2014 it expires when this process stops.\nBefore going to production, deploy to a permanent server and update the webhook URL in the portal."
@@ -6244,10 +6294,11 @@ Continuing will overwrite it.`
6244
6294
  const s4b = Y2();
6245
6295
  s4b.start("Waiting for the tunnel to start routing");
6246
6296
  const ready = await waitForTunnelReady(tunnel.url);
6247
- s4b.stop(ready ? "Tunnel is routing" : "Tunnel is slow to come online \u2014 continuing anyway");
6297
+ webhookReady = ready;
6298
+ s4b.stop(ready ? "Tunnel is routing" : "Tunnel is slow to come online");
6248
6299
  if (!ready) {
6249
6300
  M2.warn(
6250
- "The tunnel edge has not answered yet. If the checks below time out it is almost\ncertainly still warming up, not your server \u2014 wait a few seconds and re-run the wizard."
6301
+ "The tunnel edge has not answered yet. This is a Cloudflare warmup delay,\nnot your server, which is running on :3001. The wizard will wait once more\nbefore the conformance checks so a cold tunnel does not read as a failure."
6251
6302
  );
6252
6303
  }
6253
6304
  } catch (err) {
@@ -6282,16 +6333,28 @@ Continuing will overwrite it.`
6282
6333
  });
6283
6334
  }
6284
6335
  if (webhookUrl) {
6285
- M2.info("");
6286
- const s6 = Y2();
6287
- s6.start("Running conformance checks");
6288
- await new Promise((r2) => setTimeout(r2, 200));
6289
- s6.stop("Conformance checks:");
6290
- await runConformance(webhookUrl, pid, secret);
6291
- if (verifiesAccountReference) {
6292
- M2.info(
6293
- "You have declared that your receiver matches the account reference itself.\nRun `npm test` to include check-14, which sends an account reference that matches\nnothing and fails a receiver that reports it as applied. The three checks above\ncover signing and replay only."
6336
+ if (!webhookReady && tunnelBase) {
6337
+ const sr = Y2();
6338
+ sr.start("Waiting for the tunnel to start routing");
6339
+ webhookReady = await waitForTunnelReady(tunnelBase, { budgetMs: 2e4 });
6340
+ sr.stop(webhookReady ? "Tunnel is routing" : "Tunnel still not routing");
6341
+ }
6342
+ if (!webhookReady) {
6343
+ M2.warn(
6344
+ "Skipping the conformance checks: the Cloudflare tunnel edge has not come online.\nThis is a tunnel warmup delay, not your server, which is running on :3001.\nRe-run the wizard, or run `npm test` once the tunnel URL responds in a browser."
6294
6345
  );
6346
+ } else {
6347
+ M2.info("");
6348
+ const s6 = Y2();
6349
+ s6.start("Running conformance checks");
6350
+ await new Promise((r2) => setTimeout(r2, 200));
6351
+ s6.stop("Conformance checks:");
6352
+ await runConformance(webhookUrl, pid, secret);
6353
+ if (verifiesAccountReference) {
6354
+ M2.info(
6355
+ "You have declared that your receiver matches the account reference itself.\nRun `npm test` to include check-14, which sends an account reference that matches\nnothing and fails a receiver that reports it as applied. The three checks above\ncover signing and replay only."
6356
+ );
6357
+ }
6295
6358
  }
6296
6359
  }
6297
6360
  const summary = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oneaddress/setup",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
4
4
  "description": "Interactive setup wizard for OneAddress partner webhook integrations",
5
5
  "main": "dist/index.js",
6
6
  "bin": {