@oneaddress/setup 1.3.0 → 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.
- package/dist/index.js +138 -32
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -856,8 +856,31 @@ 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.
|
|
859
|
+
var WIZARD_VERSION = true ? "1.3.2" : "?";
|
|
860
|
+
function printCompactHeader() {
|
|
861
|
+
const INNER = 42;
|
|
862
|
+
const TOP = fn("\u250C") + dm("\u2500".repeat(INNER)) + fn("\u2510");
|
|
863
|
+
const BOT = fn("\u2514") + dm("\u2500".repeat(INNER)) + fn("\u2518");
|
|
864
|
+
const S3 = dm("\u2502");
|
|
865
|
+
const blank = ` ${S3}${" ".repeat(INNER)}${S3}`;
|
|
866
|
+
const row = (content, visibleLen) => ` ${S3} ${content}${" ".repeat(Math.max(0, INNER - 2 - visibleLen))}${S3}`;
|
|
867
|
+
const ver = `v${WIZARD_VERSION} \xB7 partners.oneaddress.io`;
|
|
868
|
+
console.log("");
|
|
869
|
+
console.log(" " + TOP);
|
|
870
|
+
console.log(blank);
|
|
871
|
+
console.log(row(wb("One ") + ab("Address"), "One Address".length));
|
|
872
|
+
console.log(row(mi("Partner Setup Wizard"), "Partner Setup Wizard".length));
|
|
873
|
+
console.log(row(fn(ver), ver.length));
|
|
874
|
+
console.log(blank);
|
|
875
|
+
console.log(" " + BOT);
|
|
876
|
+
console.log("");
|
|
877
|
+
}
|
|
860
878
|
function printHeader() {
|
|
879
|
+
const cols = typeof process !== "undefined" && process.stdout && process.stdout.columns ? process.stdout.columns : 80;
|
|
880
|
+
if (cols < 88) {
|
|
881
|
+
printCompactHeader();
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
861
884
|
const BAR_LEN = 85;
|
|
862
885
|
const TOP_BAR = fn("\u250C") + dm("\u2500".repeat(BAR_LEN)) + fn("\u2510");
|
|
863
886
|
const BOT_BAR = fn("\u2514") + dm("\u2500".repeat(BAR_LEN)) + fn("\u2518");
|
|
@@ -5201,7 +5224,7 @@ async function scaffold(platform, outputDir, partnerId, webhookSecret, webhookUr
|
|
|
5201
5224
|
|
|
5202
5225
|
// src/register.ts
|
|
5203
5226
|
var import_node_crypto = require("crypto");
|
|
5204
|
-
var PKG_VERSION = true ? "1.3.
|
|
5227
|
+
var PKG_VERSION = true ? "1.3.2" : "dev";
|
|
5205
5228
|
var REGISTER_URL = "https://partners.oneaddress.io/api/partner/installs";
|
|
5206
5229
|
function hmacSha256(secret, message) {
|
|
5207
5230
|
return (0, import_node_crypto.createHmac)("sha256", secret).update(message).digest("hex");
|
|
@@ -5625,7 +5648,7 @@ async function resolveBinary() {
|
|
|
5625
5648
|
return downloadCloudflared();
|
|
5626
5649
|
}
|
|
5627
5650
|
async function waitForTunnelReady(url, opts = {}) {
|
|
5628
|
-
const budgetMs = opts.budgetMs ??
|
|
5651
|
+
const budgetMs = opts.budgetMs ?? 6e4;
|
|
5629
5652
|
const intervalMs = opts.intervalMs ?? 1500;
|
|
5630
5653
|
const attemptTimeoutMs = opts.attemptTimeoutMs ?? 4e3;
|
|
5631
5654
|
const doFetch = opts.fetchImpl ?? fetch;
|
|
@@ -5686,7 +5709,13 @@ async function startTunnel(port) {
|
|
|
5686
5709
|
// src/register-url.ts
|
|
5687
5710
|
var import_node_crypto4 = require("crypto");
|
|
5688
5711
|
var PORTAL_API = "https://partners.oneaddress.io/api/partner/webhook-url";
|
|
5689
|
-
|
|
5712
|
+
function describeReadFailure(status) {
|
|
5713
|
+
if (status === 401) return "the portal rejected the signature (HTTP 401), which means your Webhook signing secret does not match the one on your profile. Copy it again from partners.oneaddress.io \u2192 Webhook \u2192 Webhook signing secret.";
|
|
5714
|
+
if (status === 404) return "the portal has no active partner for this account (HTTP 404), which usually means it is not approved yet.";
|
|
5715
|
+
if (status === 400) return "the portal rejected the request (HTTP 400).";
|
|
5716
|
+
return `the portal returned HTTP ${status}.`;
|
|
5717
|
+
}
|
|
5718
|
+
async function getExistingWebhookUrl(partnerId, webhookSecret, onError) {
|
|
5690
5719
|
try {
|
|
5691
5720
|
const ts = String(Math.floor(Date.now() / 1e3));
|
|
5692
5721
|
const sig = (0, import_node_crypto4.createHmac)("sha256", webhookSecret).update(`${ts}.`).digest("hex");
|
|
@@ -5699,14 +5728,18 @@ async function getExistingWebhookUrl(partnerId, webhookSecret) {
|
|
|
5699
5728
|
},
|
|
5700
5729
|
signal: AbortSignal.timeout(8e3)
|
|
5701
5730
|
});
|
|
5702
|
-
if (!res.ok)
|
|
5731
|
+
if (!res.ok) {
|
|
5732
|
+
onError?.(describeReadFailure(res.status));
|
|
5733
|
+
return void 0;
|
|
5734
|
+
}
|
|
5703
5735
|
const data = await res.json();
|
|
5704
5736
|
return data.webhook_url ?? null;
|
|
5705
|
-
} catch {
|
|
5737
|
+
} catch (err) {
|
|
5738
|
+
onError?.(`could not reach the portal (${err instanceof Error ? err.message : "network error"})`);
|
|
5706
5739
|
return void 0;
|
|
5707
5740
|
}
|
|
5708
5741
|
}
|
|
5709
|
-
async function getVerifiesAccountReference(partnerId, webhookSecret) {
|
|
5742
|
+
async function getVerifiesAccountReference(partnerId, webhookSecret, onError) {
|
|
5710
5743
|
try {
|
|
5711
5744
|
const ts = String(Math.floor(Date.now() / 1e3));
|
|
5712
5745
|
const sig = (0, import_node_crypto4.createHmac)("sha256", webhookSecret).update(`${ts}.`).digest("hex");
|
|
@@ -5719,16 +5752,40 @@ async function getVerifiesAccountReference(partnerId, webhookSecret) {
|
|
|
5719
5752
|
},
|
|
5720
5753
|
signal: AbortSignal.timeout(8e3)
|
|
5721
5754
|
});
|
|
5722
|
-
if (!res.ok)
|
|
5755
|
+
if (!res.ok) {
|
|
5756
|
+
onError?.(describeReadFailure(res.status));
|
|
5757
|
+
return void 0;
|
|
5758
|
+
}
|
|
5723
5759
|
const data = await res.json();
|
|
5724
5760
|
const v2 = data.verifies_account_reference;
|
|
5725
5761
|
if (v2 === true || v2 === false) return v2;
|
|
5726
5762
|
if (v2 === null) return null;
|
|
5727
5763
|
return void 0;
|
|
5728
|
-
} catch {
|
|
5764
|
+
} catch (err) {
|
|
5765
|
+
onError?.(`could not reach the portal (${err instanceof Error ? err.message : "network error"})`);
|
|
5729
5766
|
return void 0;
|
|
5730
5767
|
}
|
|
5731
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
|
+
}
|
|
5732
5789
|
async function registerWebhookUrl(partnerId, webhookSecret, webhookUrl) {
|
|
5733
5790
|
try {
|
|
5734
5791
|
const body = JSON.stringify({ webhook_url: webhookUrl });
|
|
@@ -5996,14 +6053,42 @@ async function main() {
|
|
|
5996
6053
|
M2.info(" 2. Scaffold a working webhook server for your platform");
|
|
5997
6054
|
M2.info(" 3. Get it live with a passing conformance check \u2014 in under 5 minutes");
|
|
5998
6055
|
M2.info("\nOpen partners.oneaddress.io \u2192 My Profile to find the two values below.\n");
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
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;
|
|
6004
6073
|
}
|
|
6005
|
-
|
|
6006
|
-
|
|
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
|
+
}
|
|
6007
6092
|
const privateKeyRaw = await he({
|
|
6008
6093
|
message: "Your ECDH Private Key",
|
|
6009
6094
|
placeholder: "Paste the base64 key body from My Profile, or enter a path to the .pem file",
|
|
@@ -6015,7 +6100,6 @@ async function main() {
|
|
|
6015
6100
|
}
|
|
6016
6101
|
});
|
|
6017
6102
|
assertNotCancelled(privateKeyRaw);
|
|
6018
|
-
const secret = webhookSecret.trim();
|
|
6019
6103
|
const normalised = normalisePrivateKey(privateKeyRaw.trim());
|
|
6020
6104
|
if (normalised.error) {
|
|
6021
6105
|
xe(`Private key parse failed unexpectedly: ${normalised.error}`);
|
|
@@ -6059,11 +6143,14 @@ async function main() {
|
|
|
6059
6143
|
}
|
|
6060
6144
|
}
|
|
6061
6145
|
}
|
|
6062
|
-
|
|
6146
|
+
let accountRefReason = "";
|
|
6147
|
+
const accountRefDeclaration = await getVerifiesAccountReference(pid, secret, (r2) => {
|
|
6148
|
+
accountRefReason = r2;
|
|
6149
|
+
});
|
|
6063
6150
|
const verifiesAccountReference = accountRefDeclaration === true;
|
|
6064
6151
|
if (accountRefDeclaration === void 0) {
|
|
6065
6152
|
M2.warn(
|
|
6066
|
-
"Could not read your account-reference declaration from the portal.\
|
|
6153
|
+
"Could not read your account-reference declaration from the portal.\n" + (accountRefReason ? "Reason: " + accountRefReason + "\n" : "") + "Conformance check-14 has been left out of the generated `npm test`, so a\npassing run does NOT mean your receiver matches account references correctly.\nSet it at partners.oneaddress.io \u2192 My Profile, then re-run this wizard."
|
|
6067
6154
|
);
|
|
6068
6155
|
}
|
|
6069
6156
|
const s1 = Y2();
|
|
@@ -6137,7 +6224,12 @@ async function main() {
|
|
|
6137
6224
|
if (start.output) M2.warn(start.output.slice(0, 600));
|
|
6138
6225
|
}
|
|
6139
6226
|
let webhookUrl = "";
|
|
6140
|
-
|
|
6227
|
+
let webhookReady = true;
|
|
6228
|
+
let tunnelBase = null;
|
|
6229
|
+
let existingUrlReason = "";
|
|
6230
|
+
const existingUrlResult = await getExistingWebhookUrl(pid, secret, (r2) => {
|
|
6231
|
+
existingUrlReason = r2;
|
|
6232
|
+
});
|
|
6141
6233
|
if (typeof existingUrlResult === "string") {
|
|
6142
6234
|
M2.warn(
|
|
6143
6235
|
`A webhook URL is already registered for this partner:
|
|
@@ -6156,7 +6248,7 @@ Continuing will overwrite it.`
|
|
|
6156
6248
|
}
|
|
6157
6249
|
} else if (existingUrlResult === void 0) {
|
|
6158
6250
|
M2.warn(
|
|
6159
|
-
"Could not check whether a webhook URL is already registered (portal API unreachable).
|
|
6251
|
+
"Could not check whether a webhook URL is already registered" + (existingUrlReason ? ": " + existingUrlReason : " (portal API unreachable).") + "\nAny URL you register now will overwrite an existing one without warning."
|
|
6160
6252
|
);
|
|
6161
6253
|
const continueAnyway = await ye({
|
|
6162
6254
|
message: "Continue anyway?",
|
|
@@ -6194,6 +6286,7 @@ Continuing will overwrite it.`
|
|
|
6194
6286
|
try {
|
|
6195
6287
|
const tunnel = await startTunnel(3001);
|
|
6196
6288
|
webhookUrl = `${tunnel.url}/webhook`;
|
|
6289
|
+
tunnelBase = tunnel.url;
|
|
6197
6290
|
s4.stop(`Tunnel active: ${tunnel.url}`);
|
|
6198
6291
|
M2.warn(
|
|
6199
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."
|
|
@@ -6201,10 +6294,11 @@ Continuing will overwrite it.`
|
|
|
6201
6294
|
const s4b = Y2();
|
|
6202
6295
|
s4b.start("Waiting for the tunnel to start routing");
|
|
6203
6296
|
const ready = await waitForTunnelReady(tunnel.url);
|
|
6204
|
-
|
|
6297
|
+
webhookReady = ready;
|
|
6298
|
+
s4b.stop(ready ? "Tunnel is routing" : "Tunnel is slow to come online");
|
|
6205
6299
|
if (!ready) {
|
|
6206
6300
|
M2.warn(
|
|
6207
|
-
"The tunnel edge has not answered yet.
|
|
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."
|
|
6208
6302
|
);
|
|
6209
6303
|
}
|
|
6210
6304
|
} catch (err) {
|
|
@@ -6239,16 +6333,28 @@ Continuing will overwrite it.`
|
|
|
6239
6333
|
});
|
|
6240
6334
|
}
|
|
6241
6335
|
if (webhookUrl) {
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
|
|
6248
|
-
if (
|
|
6249
|
-
M2.
|
|
6250
|
-
"
|
|
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."
|
|
6251
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
|
+
}
|
|
6252
6358
|
}
|
|
6253
6359
|
}
|
|
6254
6360
|
const summary = [
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oneaddress/setup",
|
|
3
|
-
"version": "1.3.
|
|
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": {
|
|
7
7
|
"oneaddress-setup": "dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
|
-
"LICENSE",
|
|
10
|
+
"LICENSE",
|
|
11
|
+
"dist",
|
|
11
12
|
"README.md"
|
|
12
13
|
],
|
|
13
14
|
"scripts": {
|