@oneaddress/setup 1.3.2 → 1.3.4

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 +178 -12
  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.2" : "?";
859
+ var WIZARD_VERSION = true ? "1.3.4" : "?";
860
860
  function printCompactHeader() {
861
861
  const INNER = 42;
862
862
  const TOP = fn("\u250C") + dm("\u2500".repeat(INNER)) + fn("\u2510");
@@ -1426,6 +1426,29 @@ 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
+ // Print the decrypted record as a verification line for the connection-
1439
+ // verification flow. The portal's verify page asks you to copy these lines
1440
+ // (shaped "N | name | address") out of this console and paste them at the
1441
+ // verify URL in your instruction email; decorative lines around them are
1442
+ // ignored, so pasting the whole block is fine. Returning { verification:true }
1443
+ // is what the go-live and receiver-verification decrypt proofs look for.
1444
+ const a = address as { street?: unknown; suburb?: unknown; state?: unknown; postcode?: unknown };
1445
+ const oneLine = [a.street, [a.suburb, a.state, a.postcode].filter(Boolean).join(' ')]
1446
+ .filter(Boolean).join(', ');
1447
+ console.log(\`[webhook] \${event} verification probe decrypted OK\`);
1448
+ console.log(\`[verification] \${index ?? '?'} | \${ctx.name} | \${oneLine}\`);
1449
+ return res.status(200).json({ verification: true, index });
1450
+ }
1451
+
1429
1452
  // Unknown event \u2014 acknowledge (forward compatibility)
1430
1453
  console.log(\`[webhook] Unknown event "\${event}" \u2014 acknowledged\`);
1431
1454
  return res.status(200).json({ ok: true, skipped: true });
@@ -1892,10 +1915,15 @@ async def webhook(request: Request) -> Response:
1892
1915
  session_share = body.get("session_key_share")
1893
1916
  legacy_enc = body.get("address_encrypted")
1894
1917
 
1918
+ # Decrypted display name, captured for the connection-verification probe
1919
+ # below. Identity comes ONLY from decryption, never a cleartext wire field.
1920
+ verified_name = ""
1921
+
1895
1922
  if isinstance(session_envelope, str) and isinstance(session_share, dict):
1896
1923
  try:
1897
1924
  data = _decrypt_session(session_share, session_envelope, PRIVATE_KEY, PARTNER_ID)
1898
1925
  address = data.get("new_address", {})
1926
+ verified_name = data.get("verified_name", "") or ""
1899
1927
  except Exception as e:
1900
1928
  print(f"[webhook] D5 decryption failed \u2014 check OA_PRIVATE_KEY_PEM matches key_id "
1901
1929
  f"{session_share.get('key_id', '?')}: {e}")
@@ -1904,6 +1932,7 @@ async def webhook(request: Request) -> Response:
1904
1932
  elif isinstance(legacy_enc, dict):
1905
1933
  try:
1906
1934
  address = _decrypt_address(legacy_enc, PRIVATE_KEY, PARTNER_ID)
1935
+ verified_name = address.get("fullName", "") or ""
1907
1936
  except Exception as e:
1908
1937
  print(f"[webhook] Decryption error (check OA_PRIVATE_KEY_PEM): {e}")
1909
1938
  return Response(content='{"ok":false,"error":"decryption failed - partner key mismatch"}',
@@ -1960,6 +1989,25 @@ async def webhook(request: Request) -> Response:
1960
1989
  except Exception as e:
1961
1990
  print(f"[webhook] Callback POST failed: {e}")
1962
1991
 
1992
+ elif event in ("address.test", "address.test-dispatch"):
1993
+ # OneAddress connection-verification probe. Reaching here means the D5
1994
+ # decrypt above succeeded (a wrong key returned 422), which is exactly
1995
+ # what the check proves, so answer verification=true. Anything else reads
1996
+ # as "answered but could not decrypt", which is not what happened. We also
1997
+ # print the decrypted record as a "N | name | address" line: the portal's
1998
+ # verify page asks you to copy these lines from this console and paste
1999
+ # them at the verify URL in your instruction email. Decorative lines are
2000
+ # ignored, so pasting the whole block is fine.
2001
+ idx = body.get("index")
2002
+ _locality = " ".join(str(x) for x in [address.get("suburb", ""),
2003
+ address.get("state", ""),
2004
+ address.get("postcode", "")] if x)
2005
+ one_line = ", ".join(str(p) for p in [address.get("street", ""), _locality] if p)
2006
+ print(f"[webhook] {event} verification probe decrypted OK")
2007
+ print(f"[verification] {idx if idx is not None else '?'} | {verified_name} | {one_line}")
2008
+ return Response(content=json.dumps({"verification": True, "index": body.get("index")}),
2009
+ media_type="application/json")
2010
+
1963
2011
  else:
1964
2012
  print(f"[webhook] Unknown event '{event}' \u2014 acknowledged")
1965
2013
 
@@ -2517,6 +2565,26 @@ public class OneAddressWebhookController {
2517
2565
  } else if ("address.verify".equals(event)) {
2518
2566
  handleAddressVerify(body, address, accountNumber, verifiedName, knownNames);
2519
2567
  if (dispatchId != null && !dispatchId.isBlank()) store.markProcessed(dispatchId, "verify");
2568
+ } else if ("address.test".equals(event) || "address.test-dispatch".equals(event)) {
2569
+ // OneAddress connection-verification probe. Reaching here means the
2570
+ // D5 decrypt above succeeded (a wrong key returned 422), which is
2571
+ // what the check proves, so answer verification=true. We also print
2572
+ // the decrypted record as a "N | name | address" line: the portal's
2573
+ // verify page asks you to copy these lines from this console and
2574
+ // paste them at the verify URL in your instruction email.
2575
+ if (dispatchId != null && !dispatchId.isBlank()) store.markProcessed(dispatchId, "verification:ok");
2576
+ Object idx = body.get("index");
2577
+ java.util.function.Function<String, String> fld = k -> {
2578
+ Object v = address.get(k);
2579
+ return v == null ? "" : String.valueOf(v);
2580
+ };
2581
+ String oaLoc = java.util.stream.Stream.of(fld.apply("suburb"), fld.apply("state"), fld.apply("postcode"))
2582
+ .filter(s -> !s.isBlank()).collect(java.util.stream.Collectors.joining(" "));
2583
+ String oaLine = java.util.stream.Stream.of(fld.apply("street"), oaLoc)
2584
+ .filter(s -> !s.isBlank()).collect(java.util.stream.Collectors.joining(", "));
2585
+ log.info("[OneAddress] {} verification probe decrypted OK", event);
2586
+ log.info("[verification] {} | {} | {}", idx == null ? "?" : idx, verifiedName, oaLine);
2587
+ return ResponseEntity.ok("{\\"verification\\":true,\\"index\\":" + (idx == null ? "null" : idx.toString()) + "}");
2520
2588
  } else {
2521
2589
  log.info("[OneAddress] skipping unknown event: {}", event);
2522
2590
  if (dispatchId != null && !dispatchId.isBlank()) store.markProcessed(dispatchId, "ignored:unknown-event");
@@ -3285,7 +3353,11 @@ app.MapPost("/webhooks/oneaddress", async (HttpRequest request) =>
3285
3353
 
3286
3354
  var eventType = body.TryGetProperty("event", out var evtEl) ? evtEl.GetString() : null;
3287
3355
 
3288
- if (eventType != "address.updated" && eventType != "address.verify")
3356
+ // address.test / address.test-dispatch (the connection-verification probe)
3357
+ // must pass this guard so it reaches the decrypt below; a wrong key then
3358
+ // returns 422 and only a real decrypt reaches the verification answer.
3359
+ if (eventType != "address.updated" && eventType != "address.verify"
3360
+ && eventType != "address.test" && eventType != "address.test-dispatch")
3289
3361
  {
3290
3362
  app.Logger.LogInformation("[OneAddress] skipping unknown event: {EventType}", eventType);
3291
3363
  return Results.Json(new { ok = true, skipped = true });
@@ -3366,6 +3438,27 @@ app.MapPost("/webhooks/oneaddress", async (HttpRequest request) =>
3366
3438
  app.Logger.LogWarning("[OneAddress] dispatch {DispatchId} received before but never completed \u2014 reprocessing", dispatchId);
3367
3439
  }
3368
3440
 
3441
+ if (eventType == "address.test" || eventType == "address.test-dispatch")
3442
+ {
3443
+ // OneAddress connection-verification probe. Reaching here means the D5
3444
+ // decrypt above succeeded (a wrong key returned 422), which is what the
3445
+ // check proves, so answer verification=true. We also print the decrypted
3446
+ // record as a "N | name | address" line: the portal's verify page asks
3447
+ // you to copy these lines from this console and paste them at the verify
3448
+ // URL in your instruction email.
3449
+ int? index = body.TryGetProperty("index", out var idxEl) && idxEl.ValueKind == JsonValueKind.Number
3450
+ ? idxEl.GetInt32() : (int?)null;
3451
+ static string OaStr(JsonElement a, string k) =>
3452
+ a.TryGetProperty(k, out var el) && el.ValueKind == JsonValueKind.String ? (el.GetString() ?? "") : "";
3453
+ var oaLoc = string.Join(" ", new[] { OaStr(address, "suburb"), OaStr(address, "state"), OaStr(address, "postcode") }
3454
+ .Where(s => !string.IsNullOrWhiteSpace(s)));
3455
+ var oaLine = string.Join(", ", new[] { OaStr(address, "street"), oaLoc }
3456
+ .Where(s => !string.IsNullOrWhiteSpace(s)));
3457
+ app.Logger.LogInformation("[OneAddress] {EventType} verification probe decrypted OK", eventType);
3458
+ app.Logger.LogInformation("[verification] {Line}", (index?.ToString() ?? "?") + " | " + verifiedName + " | " + oaLine);
3459
+ return Results.Json(new { verification = true, index });
3460
+ }
3461
+
3369
3462
  if (eventType == "address.updated")
3370
3463
  {
3371
3464
  // Work FIRST, respond after. Acknowledging before the write commits
@@ -4203,6 +4296,41 @@ func main() {
4203
4296
  _ = store.MarkProcessed(dispatch, "verify:"+result)
4204
4297
  }
4205
4298
 
4299
+ case "address.test", "address.test-dispatch":
4300
+ // OneAddress connection-verification probe. Reaching this case means the
4301
+ // decrypt above succeeded (a wrong key returned 422), which is what the
4302
+ // check proves, so answer verification=true rather than the ok:true below.
4303
+ // We also print the decrypted record as a "N | name | address" line: the
4304
+ // portal's verify page asks you to copy these lines from this console and
4305
+ // paste them at the verify URL in your instruction email.
4306
+ log.Printf("[webhook] %s verification probe decrypted OK", event)
4307
+ if dispatch != "" {
4308
+ _ = store.MarkProcessed(dispatch, "verification:ok")
4309
+ }
4310
+ var idx any = nil
4311
+ if v, ok := parsed["index"]; ok {
4312
+ idx = v
4313
+ }
4314
+ var locParts []string
4315
+ for _, s := range []string{str(address, "suburb"), str(address, "state"), str(address, "postcode")} {
4316
+ if s != "" {
4317
+ locParts = append(locParts, s)
4318
+ }
4319
+ }
4320
+ var lineParts []string
4321
+ for _, s := range []string{str(address, "street"), strings.Join(locParts, " ")} {
4322
+ if s != "" {
4323
+ lineParts = append(lineParts, s)
4324
+ }
4325
+ }
4326
+ idxStr := "?"
4327
+ if idx != nil {
4328
+ idxStr = fmt.Sprintf("%v", idx)
4329
+ }
4330
+ log.Printf("[verification] %s | %s | %s", idxStr, verifiedName, strings.Join(lineParts, ", "))
4331
+ jsonResp(w, 200, map[string]any{"verification": true, "index": idx})
4332
+ return
4333
+
4206
4334
  default:
4207
4335
  log.Printf("[webhook] Unknown event %q \u2014 acknowledged", event)
4208
4336
  if dispatch != "" {
@@ -4869,7 +4997,11 @@ Route::post('/webhook', function (Request $request): Response {
4869
4997
 
4870
4998
  $eventType = $body['event'] ?? '';
4871
4999
 
4872
- if ($eventType !== 'address.updated' && $eventType !== 'address.verify') {
5000
+ // address.test / address.test-dispatch (the connection-verification probe)
5001
+ // must pass this guard so it reaches the decrypt below; a wrong key then
5002
+ // returns 422 and only a real decrypt reaches the verification answer.
5003
+ if ($eventType !== 'address.updated' && $eventType !== 'address.verify'
5004
+ && $eventType !== 'address.test' && $eventType !== 'address.test-dispatch') {
4873
5005
  return response()->json(['ok' => true, 'skipped' => true]);
4874
5006
  }
4875
5007
 
@@ -5038,6 +5170,22 @@ Route::post('/webhook', function (Request $request): Response {
5038
5170
  }
5039
5171
  }
5040
5172
 
5173
+ if ($eventType === 'address.test' || $eventType === 'address.test-dispatch') {
5174
+ // OneAddress connection-verification probe. Reaching here means the D5
5175
+ // decrypt above succeeded (a wrong key returned 422), which is what the
5176
+ // check proves, so answer verification=true. We also print the decrypted
5177
+ // record as a "N | name | address" line: the portal's verify page asks
5178
+ // you to copy these lines from this console and paste them at the verify
5179
+ // URL in your instruction email.
5180
+ $oaAddr = is_array($address) ? $address : [];
5181
+ $oaKeep = static fn($s) => $s !== null && $s !== '';
5182
+ $oaLoc = implode(' ', array_filter([$oaAddr['suburb'] ?? '', $oaAddr['state'] ?? '', $oaAddr['postcode'] ?? ''], $oaKeep));
5183
+ $oaLine = implode(', ', array_filter([$oaAddr['street'] ?? '', $oaLoc], $oaKeep));
5184
+ \\Log::info('[OneAddress] verification probe decrypted OK', ['event' => $eventType]);
5185
+ \\Log::info('[verification] ' . ($body['index'] ?? '?') . ' | ' . $verifiedName . ' | ' . $oaLine);
5186
+ return response()->json(['verification' => true, 'index' => $body['index'] ?? null]);
5187
+ }
5188
+
5041
5189
  if ($eventType === 'address.updated') {
5042
5190
  // Work FIRST, respond after. Acknowledging before the write commits
5043
5191
  // marks the dispatch delivered on a failure that is never retried.
@@ -5224,7 +5372,7 @@ async function scaffold(platform, outputDir, partnerId, webhookSecret, webhookUr
5224
5372
 
5225
5373
  // src/register.ts
5226
5374
  var import_node_crypto = require("crypto");
5227
- var PKG_VERSION = true ? "1.3.2" : "dev";
5375
+ var PKG_VERSION = true ? "1.3.4" : "dev";
5228
5376
  var REGISTER_URL = "https://partners.oneaddress.io/api/partner/installs";
5229
5377
  function hmacSha256(secret, message) {
5230
5378
  return (0, import_node_crypto.createHmac)("sha256", secret).update(message).digest("hex");
@@ -6284,21 +6432,36 @@ Continuing will overwrite it.`
6284
6432
  s4.start("Starting Cloudflare Tunnel");
6285
6433
  onCleanup(stopTunnel);
6286
6434
  try {
6287
- const tunnel = await startTunnel(3001);
6435
+ let tunnel = await startTunnel(3001);
6288
6436
  webhookUrl = `${tunnel.url}/webhook`;
6289
6437
  tunnelBase = tunnel.url;
6290
6438
  s4.stop(`Tunnel active: ${tunnel.url}`);
6291
6439
  M2.warn(
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."
6440
+ "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."
6293
6441
  );
6294
6442
  const s4b = Y2();
6295
6443
  s4b.start("Waiting for the tunnel to start routing");
6296
- const ready = await waitForTunnelReady(tunnel.url);
6297
- webhookReady = ready;
6298
- s4b.stop(ready ? "Tunnel is routing" : "Tunnel is slow to come online");
6299
- if (!ready) {
6444
+ webhookReady = await waitForTunnelReady(tunnel.url);
6445
+ if (!webhookReady) {
6446
+ s4b.stop("First tunnel is slow to route, trying a fresh one");
6447
+ stopTunnel();
6448
+ tunnel = await startTunnel(3001);
6449
+ webhookUrl = `${tunnel.url}/webhook`;
6450
+ tunnelBase = tunnel.url;
6451
+ const s4c = Y2();
6452
+ s4c.start(`Waiting for the new tunnel to route: ${tunnel.url}`);
6453
+ webhookReady = await waitForTunnelReady(tunnel.url, { budgetMs: 45e3 });
6454
+ s4c.stop(webhookReady ? "Tunnel is routing" : "Tunnel still not routing");
6455
+ } else {
6456
+ s4b.stop("Tunnel is routing");
6457
+ }
6458
+ if (!webhookReady) {
6300
6459
  M2.warn(
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."
6460
+ `The tunnel edge is still not routing after a retry. This is almost always
6461
+ one of two things, and neither is your server (it is running on :3001):
6462
+ 1. Open ${tunnel.url}/health in a browser. If it never loads, your network
6463
+ is blocking cloudflared and no wait will fix it, so try a different network.
6464
+ 2. The tunnel only lives while this wizard runs, so keep this window open.`
6302
6465
  );
6303
6466
  }
6304
6467
  } catch (err) {
@@ -6341,7 +6504,10 @@ Continuing will overwrite it.`
6341
6504
  }
6342
6505
  if (!webhookReady) {
6343
6506
  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."
6507
+ `Skipping the conformance checks: the tunnel edge is not routing.
6508
+ Your server is fine, it is running on :3001. This is either your network
6509
+ blocking cloudflared (open ${tunnelBase}/health in a browser to check) or the
6510
+ tunnel needing this wizard to stay open. Re-run once that URL loads in a browser.`
6345
6511
  );
6346
6512
  } else {
6347
6513
  M2.info("");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oneaddress/setup",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "description": "Interactive setup wizard for OneAddress partner webhook integrations",
5
5
  "main": "dist/index.js",
6
6
  "bin": {