@oneaddress/setup 1.3.1 → 1.3.3
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 +181 -28
- 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.
|
|
859
|
+
var WIZARD_VERSION = true ? "1.3.3" : "?";
|
|
860
860
|
function printCompactHeader() {
|
|
861
861
|
const INNER = 42;
|
|
862
862
|
const TOP = fn("\u250C") + dm("\u2500".repeat(INNER)) + fn("\u2510");
|
|
@@ -1426,6 +1426,19 @@ app.post('/webhook', async (req: Request, res: Response) => {
|
|
|
1426
1426
|
return res.status(200).json({ ok: true });
|
|
1427
1427
|
}
|
|
1428
1428
|
|
|
1429
|
+
// \u2500\u2500 address.test / address.test-dispatch: OneAddress connection-verification
|
|
1430
|
+
// A fabricated test record wrapped to your key. Reaching this line proves
|
|
1431
|
+
// PARTNER_PRIVATE_KEY_PEM decrypted it: a wrong key would have failed the D5
|
|
1432
|
+
// decrypt above and returned 422, never here. Answer { verification: true },
|
|
1433
|
+
// which is what the receiver-verification and go-live checks look for. Return
|
|
1434
|
+
// anything else (including the forward-compat skip below) and the check reads
|
|
1435
|
+
// as "answered but could not decrypt", which is not what happened.
|
|
1436
|
+
if (event === 'address.test' || event === 'address.test-dispatch') {
|
|
1437
|
+
const index = typeof body.index === 'number' ? body.index : undefined;
|
|
1438
|
+
console.log(\`[webhook] \${event} verification probe decrypted OK\`);
|
|
1439
|
+
return res.status(200).json({ verification: true, index });
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1429
1442
|
// Unknown event \u2014 acknowledge (forward compatibility)
|
|
1430
1443
|
console.log(\`[webhook] Unknown event "\${event}" \u2014 acknowledged\`);
|
|
1431
1444
|
return res.status(200).json({ ok: true, skipped: true });
|
|
@@ -1960,6 +1973,15 @@ async def webhook(request: Request) -> Response:
|
|
|
1960
1973
|
except Exception as e:
|
|
1961
1974
|
print(f"[webhook] Callback POST failed: {e}")
|
|
1962
1975
|
|
|
1976
|
+
elif event in ("address.test", "address.test-dispatch"):
|
|
1977
|
+
# OneAddress connection-verification probe. Reaching here means the D5
|
|
1978
|
+
# decrypt above succeeded (a wrong key returned 422), which is exactly
|
|
1979
|
+
# what the check proves, so answer verification=true. Anything else reads
|
|
1980
|
+
# as "answered but could not decrypt", which is not what happened.
|
|
1981
|
+
print(f"[webhook] {event} verification probe decrypted OK")
|
|
1982
|
+
return Response(content=json.dumps({"verification": True, "index": body.get("index")}),
|
|
1983
|
+
media_type="application/json")
|
|
1984
|
+
|
|
1963
1985
|
else:
|
|
1964
1986
|
print(f"[webhook] Unknown event '{event}' \u2014 acknowledged")
|
|
1965
1987
|
|
|
@@ -2517,6 +2539,14 @@ public class OneAddressWebhookController {
|
|
|
2517
2539
|
} else if ("address.verify".equals(event)) {
|
|
2518
2540
|
handleAddressVerify(body, address, accountNumber, verifiedName, knownNames);
|
|
2519
2541
|
if (dispatchId != null && !dispatchId.isBlank()) store.markProcessed(dispatchId, "verify");
|
|
2542
|
+
} else if ("address.test".equals(event) || "address.test-dispatch".equals(event)) {
|
|
2543
|
+
// OneAddress connection-verification probe. Reaching here means the
|
|
2544
|
+
// D5 decrypt above succeeded (a wrong key returned 422), which is
|
|
2545
|
+
// what the check proves, so answer verification=true.
|
|
2546
|
+
if (dispatchId != null && !dispatchId.isBlank()) store.markProcessed(dispatchId, "verification:ok");
|
|
2547
|
+
Object idx = body.get("index");
|
|
2548
|
+
log.info("[OneAddress] {} verification probe decrypted OK", event);
|
|
2549
|
+
return ResponseEntity.ok("{\\"verification\\":true,\\"index\\":" + (idx == null ? "null" : idx.toString()) + "}");
|
|
2520
2550
|
} else {
|
|
2521
2551
|
log.info("[OneAddress] skipping unknown event: {}", event);
|
|
2522
2552
|
if (dispatchId != null && !dispatchId.isBlank()) store.markProcessed(dispatchId, "ignored:unknown-event");
|
|
@@ -3285,7 +3315,11 @@ app.MapPost("/webhooks/oneaddress", async (HttpRequest request) =>
|
|
|
3285
3315
|
|
|
3286
3316
|
var eventType = body.TryGetProperty("event", out var evtEl) ? evtEl.GetString() : null;
|
|
3287
3317
|
|
|
3288
|
-
|
|
3318
|
+
// address.test / address.test-dispatch (the connection-verification probe)
|
|
3319
|
+
// must pass this guard so it reaches the decrypt below; a wrong key then
|
|
3320
|
+
// returns 422 and only a real decrypt reaches the verification answer.
|
|
3321
|
+
if (eventType != "address.updated" && eventType != "address.verify"
|
|
3322
|
+
&& eventType != "address.test" && eventType != "address.test-dispatch")
|
|
3289
3323
|
{
|
|
3290
3324
|
app.Logger.LogInformation("[OneAddress] skipping unknown event: {EventType}", eventType);
|
|
3291
3325
|
return Results.Json(new { ok = true, skipped = true });
|
|
@@ -3366,6 +3400,17 @@ app.MapPost("/webhooks/oneaddress", async (HttpRequest request) =>
|
|
|
3366
3400
|
app.Logger.LogWarning("[OneAddress] dispatch {DispatchId} received before but never completed \u2014 reprocessing", dispatchId);
|
|
3367
3401
|
}
|
|
3368
3402
|
|
|
3403
|
+
if (eventType == "address.test" || eventType == "address.test-dispatch")
|
|
3404
|
+
{
|
|
3405
|
+
// OneAddress connection-verification probe. Reaching here means the D5
|
|
3406
|
+
// decrypt above succeeded (a wrong key returned 422), which is what the
|
|
3407
|
+
// check proves, so answer verification=true.
|
|
3408
|
+
int? index = body.TryGetProperty("index", out var idxEl) && idxEl.ValueKind == JsonValueKind.Number
|
|
3409
|
+
? idxEl.GetInt32() : (int?)null;
|
|
3410
|
+
app.Logger.LogInformation("[OneAddress] {EventType} verification probe decrypted OK", eventType);
|
|
3411
|
+
return Results.Json(new { verification = true, index });
|
|
3412
|
+
}
|
|
3413
|
+
|
|
3369
3414
|
if (eventType == "address.updated")
|
|
3370
3415
|
{
|
|
3371
3416
|
// Work FIRST, respond after. Acknowledging before the write commits
|
|
@@ -4203,6 +4248,21 @@ func main() {
|
|
|
4203
4248
|
_ = store.MarkProcessed(dispatch, "verify:"+result)
|
|
4204
4249
|
}
|
|
4205
4250
|
|
|
4251
|
+
case "address.test", "address.test-dispatch":
|
|
4252
|
+
// OneAddress connection-verification probe. Reaching this case means the
|
|
4253
|
+
// decrypt above succeeded (a wrong key returned 422), which is what the
|
|
4254
|
+
// check proves, so answer verification=true rather than the ok:true below.
|
|
4255
|
+
log.Printf("[webhook] %s verification probe decrypted OK", event)
|
|
4256
|
+
if dispatch != "" {
|
|
4257
|
+
_ = store.MarkProcessed(dispatch, "verification:ok")
|
|
4258
|
+
}
|
|
4259
|
+
var idx any = nil
|
|
4260
|
+
if v, ok := parsed["index"]; ok {
|
|
4261
|
+
idx = v
|
|
4262
|
+
}
|
|
4263
|
+
jsonResp(w, 200, map[string]any{"verification": true, "index": idx})
|
|
4264
|
+
return
|
|
4265
|
+
|
|
4206
4266
|
default:
|
|
4207
4267
|
log.Printf("[webhook] Unknown event %q \u2014 acknowledged", event)
|
|
4208
4268
|
if dispatch != "" {
|
|
@@ -4869,7 +4929,11 @@ Route::post('/webhook', function (Request $request): Response {
|
|
|
4869
4929
|
|
|
4870
4930
|
$eventType = $body['event'] ?? '';
|
|
4871
4931
|
|
|
4872
|
-
|
|
4932
|
+
// address.test / address.test-dispatch (the connection-verification probe)
|
|
4933
|
+
// must pass this guard so it reaches the decrypt below; a wrong key then
|
|
4934
|
+
// returns 422 and only a real decrypt reaches the verification answer.
|
|
4935
|
+
if ($eventType !== 'address.updated' && $eventType !== 'address.verify'
|
|
4936
|
+
&& $eventType !== 'address.test' && $eventType !== 'address.test-dispatch') {
|
|
4873
4937
|
return response()->json(['ok' => true, 'skipped' => true]);
|
|
4874
4938
|
}
|
|
4875
4939
|
|
|
@@ -5038,6 +5102,14 @@ Route::post('/webhook', function (Request $request): Response {
|
|
|
5038
5102
|
}
|
|
5039
5103
|
}
|
|
5040
5104
|
|
|
5105
|
+
if ($eventType === 'address.test' || $eventType === 'address.test-dispatch') {
|
|
5106
|
+
// OneAddress connection-verification probe. Reaching here means the D5
|
|
5107
|
+
// decrypt above succeeded (a wrong key returned 422), which is what the
|
|
5108
|
+
// check proves, so answer verification=true.
|
|
5109
|
+
\\Log::info('[OneAddress] verification probe decrypted OK', ['event' => $eventType]);
|
|
5110
|
+
return response()->json(['verification' => true, 'index' => $body['index'] ?? null]);
|
|
5111
|
+
}
|
|
5112
|
+
|
|
5041
5113
|
if ($eventType === 'address.updated') {
|
|
5042
5114
|
// Work FIRST, respond after. Acknowledging before the write commits
|
|
5043
5115
|
// marks the dispatch delivered on a failure that is never retried.
|
|
@@ -5224,7 +5296,7 @@ async function scaffold(platform, outputDir, partnerId, webhookSecret, webhookUr
|
|
|
5224
5296
|
|
|
5225
5297
|
// src/register.ts
|
|
5226
5298
|
var import_node_crypto = require("crypto");
|
|
5227
|
-
var PKG_VERSION = true ? "1.3.
|
|
5299
|
+
var PKG_VERSION = true ? "1.3.3" : "dev";
|
|
5228
5300
|
var REGISTER_URL = "https://partners.oneaddress.io/api/partner/installs";
|
|
5229
5301
|
function hmacSha256(secret, message) {
|
|
5230
5302
|
return (0, import_node_crypto.createHmac)("sha256", secret).update(message).digest("hex");
|
|
@@ -5648,7 +5720,7 @@ async function resolveBinary() {
|
|
|
5648
5720
|
return downloadCloudflared();
|
|
5649
5721
|
}
|
|
5650
5722
|
async function waitForTunnelReady(url, opts = {}) {
|
|
5651
|
-
const budgetMs = opts.budgetMs ??
|
|
5723
|
+
const budgetMs = opts.budgetMs ?? 6e4;
|
|
5652
5724
|
const intervalMs = opts.intervalMs ?? 1500;
|
|
5653
5725
|
const attemptTimeoutMs = opts.attemptTimeoutMs ?? 4e3;
|
|
5654
5726
|
const doFetch = opts.fetchImpl ?? fetch;
|
|
@@ -5766,6 +5838,26 @@ async function getVerifiesAccountReference(partnerId, webhookSecret, onError) {
|
|
|
5766
5838
|
return void 0;
|
|
5767
5839
|
}
|
|
5768
5840
|
}
|
|
5841
|
+
async function verifyWebhookSecret(partnerId, webhookSecret) {
|
|
5842
|
+
try {
|
|
5843
|
+
const ts = String(Math.floor(Date.now() / 1e3));
|
|
5844
|
+
const sig = (0, import_node_crypto4.createHmac)("sha256", webhookSecret).update(`${ts}.`).digest("hex");
|
|
5845
|
+
const res = await fetch(PORTAL_API, {
|
|
5846
|
+
method: "GET",
|
|
5847
|
+
headers: {
|
|
5848
|
+
"X-OneAddress-Timestamp": ts,
|
|
5849
|
+
"X-OneAddress-Signature": sig,
|
|
5850
|
+
"X-OneAddress-Partner": partnerId
|
|
5851
|
+
},
|
|
5852
|
+
signal: AbortSignal.timeout(8e3)
|
|
5853
|
+
});
|
|
5854
|
+
if (res.ok) return "ok";
|
|
5855
|
+
if (res.status === 401 || res.status === 403) return "bad_secret";
|
|
5856
|
+
return "unreachable";
|
|
5857
|
+
} catch {
|
|
5858
|
+
return "unreachable";
|
|
5859
|
+
}
|
|
5860
|
+
}
|
|
5769
5861
|
async function registerWebhookUrl(partnerId, webhookSecret, webhookUrl) {
|
|
5770
5862
|
try {
|
|
5771
5863
|
const body = JSON.stringify({ webhook_url: webhookUrl });
|
|
@@ -6033,14 +6125,42 @@ async function main() {
|
|
|
6033
6125
|
M2.info(" 2. Scaffold a working webhook server for your platform");
|
|
6034
6126
|
M2.info(" 3. Get it live with a passing conformance check \u2014 in under 5 minutes");
|
|
6035
6127
|
M2.info("\nOpen partners.oneaddress.io \u2192 My Profile to find the two values below.\n");
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6128
|
+
let secret;
|
|
6129
|
+
for (let attempt = 1; ; attempt++) {
|
|
6130
|
+
const webhookSecret = await ge({
|
|
6131
|
+
message: attempt === 1 ? "Your Webhook signing secret (min 20 chars)" : `Your Webhook signing secret (attempt ${attempt})`,
|
|
6132
|
+
validate: (v2) => {
|
|
6133
|
+
if (!v2.trim()) return "Webhook signing secret is required";
|
|
6134
|
+
if (v2.length < 20) return "That looks too short \u2014 paste the full secret from My Profile";
|
|
6135
|
+
}
|
|
6136
|
+
});
|
|
6137
|
+
assertNotCancelled(webhookSecret);
|
|
6138
|
+
secret = webhookSecret.trim();
|
|
6139
|
+
const sv = Y2();
|
|
6140
|
+
sv.start("Checking your Webhook signing secret");
|
|
6141
|
+
const check = await verifyWebhookSecret(pid, secret);
|
|
6142
|
+
if (check === "ok") {
|
|
6143
|
+
sv.stop("Webhook signing secret verified");
|
|
6144
|
+
break;
|
|
6041
6145
|
}
|
|
6042
|
-
|
|
6043
|
-
|
|
6146
|
+
if (check === "unreachable") {
|
|
6147
|
+
sv.stop("Could not verify the secret against the portal, continuing");
|
|
6148
|
+
M2.warn(
|
|
6149
|
+
"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."
|
|
6150
|
+
);
|
|
6151
|
+
break;
|
|
6152
|
+
}
|
|
6153
|
+
sv.stop("That Webhook signing secret was rejected");
|
|
6154
|
+
if (attempt >= 3) {
|
|
6155
|
+
xe(
|
|
6156
|
+
"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."
|
|
6157
|
+
);
|
|
6158
|
+
process.exit(1);
|
|
6159
|
+
}
|
|
6160
|
+
M2.error(
|
|
6161
|
+
"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."
|
|
6162
|
+
);
|
|
6163
|
+
}
|
|
6044
6164
|
const privateKeyRaw = await he({
|
|
6045
6165
|
message: "Your ECDH Private Key",
|
|
6046
6166
|
placeholder: "Paste the base64 key body from My Profile, or enter a path to the .pem file",
|
|
@@ -6052,7 +6172,6 @@ async function main() {
|
|
|
6052
6172
|
}
|
|
6053
6173
|
});
|
|
6054
6174
|
assertNotCancelled(privateKeyRaw);
|
|
6055
|
-
const secret = webhookSecret.trim();
|
|
6056
6175
|
const normalised = normalisePrivateKey(privateKeyRaw.trim());
|
|
6057
6176
|
if (normalised.error) {
|
|
6058
6177
|
xe(`Private key parse failed unexpectedly: ${normalised.error}`);
|
|
@@ -6177,6 +6296,8 @@ async function main() {
|
|
|
6177
6296
|
if (start.output) M2.warn(start.output.slice(0, 600));
|
|
6178
6297
|
}
|
|
6179
6298
|
let webhookUrl = "";
|
|
6299
|
+
let webhookReady = true;
|
|
6300
|
+
let tunnelBase = null;
|
|
6180
6301
|
let existingUrlReason = "";
|
|
6181
6302
|
const existingUrlResult = await getExistingWebhookUrl(pid, secret, (r2) => {
|
|
6182
6303
|
existingUrlReason = r2;
|
|
@@ -6235,19 +6356,36 @@ Continuing will overwrite it.`
|
|
|
6235
6356
|
s4.start("Starting Cloudflare Tunnel");
|
|
6236
6357
|
onCleanup(stopTunnel);
|
|
6237
6358
|
try {
|
|
6238
|
-
|
|
6359
|
+
let tunnel = await startTunnel(3001);
|
|
6239
6360
|
webhookUrl = `${tunnel.url}/webhook`;
|
|
6361
|
+
tunnelBase = tunnel.url;
|
|
6240
6362
|
s4.stop(`Tunnel active: ${tunnel.url}`);
|
|
6241
6363
|
M2.warn(
|
|
6242
|
-
"IMPORTANT: This tunnel URL is temporary
|
|
6364
|
+
"IMPORTANT: This tunnel URL is temporary. It lives only while this wizard\nkeeps running, and every run gets a new address. Before going to production,\ndeploy to a permanent server and update the webhook URL in the portal."
|
|
6243
6365
|
);
|
|
6244
6366
|
const s4b = Y2();
|
|
6245
6367
|
s4b.start("Waiting for the tunnel to start routing");
|
|
6246
|
-
|
|
6247
|
-
|
|
6248
|
-
|
|
6368
|
+
webhookReady = await waitForTunnelReady(tunnel.url);
|
|
6369
|
+
if (!webhookReady) {
|
|
6370
|
+
s4b.stop("First tunnel is slow to route, trying a fresh one");
|
|
6371
|
+
stopTunnel();
|
|
6372
|
+
tunnel = await startTunnel(3001);
|
|
6373
|
+
webhookUrl = `${tunnel.url}/webhook`;
|
|
6374
|
+
tunnelBase = tunnel.url;
|
|
6375
|
+
const s4c = Y2();
|
|
6376
|
+
s4c.start(`Waiting for the new tunnel to route: ${tunnel.url}`);
|
|
6377
|
+
webhookReady = await waitForTunnelReady(tunnel.url, { budgetMs: 45e3 });
|
|
6378
|
+
s4c.stop(webhookReady ? "Tunnel is routing" : "Tunnel still not routing");
|
|
6379
|
+
} else {
|
|
6380
|
+
s4b.stop("Tunnel is routing");
|
|
6381
|
+
}
|
|
6382
|
+
if (!webhookReady) {
|
|
6249
6383
|
M2.warn(
|
|
6250
|
-
|
|
6384
|
+
`The tunnel edge is still not routing after a retry. This is almost always
|
|
6385
|
+
one of two things, and neither is your server (it is running on :3001):
|
|
6386
|
+
1. Open ${tunnel.url}/health in a browser. If it never loads, your network
|
|
6387
|
+
is blocking cloudflared and no wait will fix it, so try a different network.
|
|
6388
|
+
2. The tunnel only lives while this wizard runs, so keep this window open.`
|
|
6251
6389
|
);
|
|
6252
6390
|
}
|
|
6253
6391
|
} catch (err) {
|
|
@@ -6282,16 +6420,31 @@ Continuing will overwrite it.`
|
|
|
6282
6420
|
});
|
|
6283
6421
|
}
|
|
6284
6422
|
if (webhookUrl) {
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
if (
|
|
6292
|
-
M2.
|
|
6293
|
-
|
|
6423
|
+
if (!webhookReady && tunnelBase) {
|
|
6424
|
+
const sr = Y2();
|
|
6425
|
+
sr.start("Waiting for the tunnel to start routing");
|
|
6426
|
+
webhookReady = await waitForTunnelReady(tunnelBase, { budgetMs: 2e4 });
|
|
6427
|
+
sr.stop(webhookReady ? "Tunnel is routing" : "Tunnel still not routing");
|
|
6428
|
+
}
|
|
6429
|
+
if (!webhookReady) {
|
|
6430
|
+
M2.warn(
|
|
6431
|
+
`Skipping the conformance checks: the tunnel edge is not routing.
|
|
6432
|
+
Your server is fine, it is running on :3001. This is either your network
|
|
6433
|
+
blocking cloudflared (open ${tunnelBase}/health in a browser to check) or the
|
|
6434
|
+
tunnel needing this wizard to stay open. Re-run once that URL loads in a browser.`
|
|
6294
6435
|
);
|
|
6436
|
+
} else {
|
|
6437
|
+
M2.info("");
|
|
6438
|
+
const s6 = Y2();
|
|
6439
|
+
s6.start("Running conformance checks");
|
|
6440
|
+
await new Promise((r2) => setTimeout(r2, 200));
|
|
6441
|
+
s6.stop("Conformance checks:");
|
|
6442
|
+
await runConformance(webhookUrl, pid, secret);
|
|
6443
|
+
if (verifiesAccountReference) {
|
|
6444
|
+
M2.info(
|
|
6445
|
+
"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."
|
|
6446
|
+
);
|
|
6447
|
+
}
|
|
6295
6448
|
}
|
|
6296
6449
|
}
|
|
6297
6450
|
const summary = [
|