@threadbase-sh/streamer 1.62.0 → 1.63.0

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 CHANGED
@@ -11548,6 +11548,55 @@ function parseE2eeRequest(raw) {
11548
11548
  return { version: v, message1 };
11549
11549
  }
11550
11550
 
11551
+ // src/e2ee/pair-payload.ts
11552
+ var MAX_DEVICE_NAME_CHARS = 100;
11553
+ function parseE2eeMsg1Payload(payload) {
11554
+ let raw;
11555
+ try {
11556
+ raw = JSON.parse(payload.toString("utf-8"));
11557
+ } catch {
11558
+ throw new E2eeRequestError("E2EE_MALFORMED", "e2ee message 1 payload is not valid JSON");
11559
+ }
11560
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
11561
+ throw new E2eeRequestError("E2EE_MALFORMED", "e2ee message 1 payload must be an object");
11562
+ }
11563
+ const { v, deviceName, readOnly } = raw;
11564
+ if (typeof v !== "number" || !Number.isInteger(v)) {
11565
+ throw new E2eeRequestError("E2EE_MALFORMED", "e2ee message 1 payload v must be an integer");
11566
+ }
11567
+ if (v !== E2EE_EXCHANGE_VERSION) {
11568
+ throw new E2eeRequestError(
11569
+ "E2EE_VERSION_UNSUPPORTED",
11570
+ `e2ee message 1 payload v ${v} is not supported; this server speaks ${E2EE_EXCHANGE_VERSION}`
11571
+ );
11572
+ }
11573
+ if (typeof readOnly !== "boolean") {
11574
+ throw new E2eeRequestError(
11575
+ "E2EE_MALFORMED",
11576
+ "e2ee message 1 payload readOnly must be a boolean"
11577
+ );
11578
+ }
11579
+ if (deviceName !== void 0 && deviceName !== null && typeof deviceName !== "string") {
11580
+ throw new E2eeRequestError(
11581
+ "E2EE_MALFORMED",
11582
+ "e2ee message 1 payload deviceName must be a string when present"
11583
+ );
11584
+ }
11585
+ return {
11586
+ version: v,
11587
+ deviceName: typeof deviceName === "string" ? deviceName.slice(0, MAX_DEVICE_NAME_CHARS) : null,
11588
+ readOnly
11589
+ };
11590
+ }
11591
+ function encodeE2eeMsg2Payload(fields) {
11592
+ const payload = {
11593
+ v: E2EE_EXCHANGE_VERSION,
11594
+ ...fields,
11595
+ e2eeRequired: true
11596
+ };
11597
+ return Buffer.from(JSON.stringify(payload), "utf-8");
11598
+ }
11599
+
11551
11600
  // src/external-tails.ts
11552
11601
  import { statSync as statSync6 } from "fs";
11553
11602
  var EXTERNAL_TAIL_RECENCY_MS = RESUME_BUSY_WINDOW_MS;
@@ -16658,6 +16707,7 @@ var StreamerServer = class {
16658
16707
  return;
16659
16708
  }
16660
16709
  let handshake = null;
16710
+ let registration = null;
16661
16711
  if (e2eeRequest) {
16662
16712
  try {
16663
16713
  handshake = readMessage1({
@@ -16675,6 +16725,13 @@ var StreamerServer = class {
16675
16725
  });
16676
16726
  return;
16677
16727
  }
16728
+ try {
16729
+ registration = parseE2eeMsg1Payload(handshake.payload);
16730
+ } catch (err) {
16731
+ const e = err;
16732
+ json(res, 400, { error: e.message, code: e.code });
16733
+ return;
16734
+ }
16678
16735
  }
16679
16736
  let sealed;
16680
16737
  try {
@@ -16697,8 +16754,9 @@ var StreamerServer = class {
16697
16754
  });
16698
16755
  let device = null;
16699
16756
  try {
16700
- const name = typeof body?.deviceName === "string" ? body.deviceName.slice(0, 100) : null;
16701
- const preset = body?.readOnly === true ? "read-only" : "full";
16757
+ const name = registration ? registration.deviceName : typeof body?.deviceName === "string" ? body.deviceName.slice(0, 100) : null;
16758
+ const readOnly = registration ? registration.readOnly : body?.readOnly === true;
16759
+ const preset = readOnly ? "read-only" : "full";
16702
16760
  device = this.devicesRepo?.register({
16703
16761
  publicKey: clientPublicKey,
16704
16762
  name,
@@ -16721,23 +16779,34 @@ var StreamerServer = class {
16721
16779
  err
16722
16780
  });
16723
16781
  }
16782
+ if (handshake && !device) {
16783
+ this.log.error("[pair] E2EE pairing failed: the device could not be registered", {
16784
+ event: "pair.e2ee_registration_failed",
16785
+ ip
16786
+ });
16787
+ json(res, 500, {
16788
+ error: "Pairing failed: this server could not register the device. Scan a fresh pairing code and try again.",
16789
+ code: "E2EE_REGISTRATION_FAILED"
16790
+ });
16791
+ return;
16792
+ }
16724
16793
  let e2eeResponse = null;
16725
- if (handshake) {
16794
+ if (handshake && device) {
16726
16795
  try {
16727
16796
  const { message2 } = writeMessage2(
16728
16797
  handshake,
16729
- Buffer.from(
16730
- JSON.stringify({
16731
- v: E2EE_EXCHANGE_VERSION,
16732
- deviceId: device?.deviceId ?? null,
16733
- serverVersion: getVersion(),
16734
- // Always true on this path: completing a handshake is what pins
16735
- // the device, and design.md §6.3 says nothing a client sends
16736
- // ever clears it.
16737
- e2eeRequired: true
16738
- }),
16739
- "utf-8"
16740
- )
16798
+ // Every result a new client persists or presents as verified, so it
16799
+ // never has to trust the outer, unauthenticated copy of a credential.
16800
+ // The outer response still carries those copies for released builds;
16801
+ // this is what the new client actually reads.
16802
+ encodeE2eeMsg2Payload({
16803
+ deviceId: device.deviceId,
16804
+ deviceToken: device.deviceToken,
16805
+ capabilities: device.capabilities,
16806
+ publicUrl: this.publicUrl,
16807
+ machineName: hostname3(),
16808
+ serverVersion: getVersion()
16809
+ })
16741
16810
  );
16742
16811
  e2eeResponse = { v: E2EE_EXCHANGE_VERSION, noise: message2.toString("base64") };
16743
16812
  } catch (err) {