@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.cjs CHANGED
@@ -11579,6 +11579,55 @@ function parseE2eeRequest(raw) {
11579
11579
  return { version: v, message1 };
11580
11580
  }
11581
11581
 
11582
+ // src/e2ee/pair-payload.ts
11583
+ var MAX_DEVICE_NAME_CHARS = 100;
11584
+ function parseE2eeMsg1Payload(payload) {
11585
+ let raw;
11586
+ try {
11587
+ raw = JSON.parse(payload.toString("utf-8"));
11588
+ } catch {
11589
+ throw new E2eeRequestError("E2EE_MALFORMED", "e2ee message 1 payload is not valid JSON");
11590
+ }
11591
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
11592
+ throw new E2eeRequestError("E2EE_MALFORMED", "e2ee message 1 payload must be an object");
11593
+ }
11594
+ const { v, deviceName, readOnly } = raw;
11595
+ if (typeof v !== "number" || !Number.isInteger(v)) {
11596
+ throw new E2eeRequestError("E2EE_MALFORMED", "e2ee message 1 payload v must be an integer");
11597
+ }
11598
+ if (v !== E2EE_EXCHANGE_VERSION) {
11599
+ throw new E2eeRequestError(
11600
+ "E2EE_VERSION_UNSUPPORTED",
11601
+ `e2ee message 1 payload v ${v} is not supported; this server speaks ${E2EE_EXCHANGE_VERSION}`
11602
+ );
11603
+ }
11604
+ if (typeof readOnly !== "boolean") {
11605
+ throw new E2eeRequestError(
11606
+ "E2EE_MALFORMED",
11607
+ "e2ee message 1 payload readOnly must be a boolean"
11608
+ );
11609
+ }
11610
+ if (deviceName !== void 0 && deviceName !== null && typeof deviceName !== "string") {
11611
+ throw new E2eeRequestError(
11612
+ "E2EE_MALFORMED",
11613
+ "e2ee message 1 payload deviceName must be a string when present"
11614
+ );
11615
+ }
11616
+ return {
11617
+ version: v,
11618
+ deviceName: typeof deviceName === "string" ? deviceName.slice(0, MAX_DEVICE_NAME_CHARS) : null,
11619
+ readOnly
11620
+ };
11621
+ }
11622
+ function encodeE2eeMsg2Payload(fields) {
11623
+ const payload = {
11624
+ v: E2EE_EXCHANGE_VERSION,
11625
+ ...fields,
11626
+ e2eeRequired: true
11627
+ };
11628
+ return Buffer.from(JSON.stringify(payload), "utf-8");
11629
+ }
11630
+
11582
11631
  // src/external-tails.ts
11583
11632
  var import_fs16 = require("fs");
11584
11633
  var EXTERNAL_TAIL_RECENCY_MS = RESUME_BUSY_WINDOW_MS;
@@ -16687,6 +16736,7 @@ var StreamerServer = class {
16687
16736
  return;
16688
16737
  }
16689
16738
  let handshake = null;
16739
+ let registration = null;
16690
16740
  if (e2eeRequest) {
16691
16741
  try {
16692
16742
  handshake = readMessage1({
@@ -16704,6 +16754,13 @@ var StreamerServer = class {
16704
16754
  });
16705
16755
  return;
16706
16756
  }
16757
+ try {
16758
+ registration = parseE2eeMsg1Payload(handshake.payload);
16759
+ } catch (err) {
16760
+ const e = err;
16761
+ json(res, 400, { error: e.message, code: e.code });
16762
+ return;
16763
+ }
16707
16764
  }
16708
16765
  let sealed;
16709
16766
  try {
@@ -16726,8 +16783,9 @@ var StreamerServer = class {
16726
16783
  });
16727
16784
  let device = null;
16728
16785
  try {
16729
- const name = typeof body?.deviceName === "string" ? body.deviceName.slice(0, 100) : null;
16730
- const preset = body?.readOnly === true ? "read-only" : "full";
16786
+ const name = registration ? registration.deviceName : typeof body?.deviceName === "string" ? body.deviceName.slice(0, 100) : null;
16787
+ const readOnly = registration ? registration.readOnly : body?.readOnly === true;
16788
+ const preset = readOnly ? "read-only" : "full";
16731
16789
  device = this.devicesRepo?.register({
16732
16790
  publicKey: clientPublicKey,
16733
16791
  name,
@@ -16750,23 +16808,34 @@ var StreamerServer = class {
16750
16808
  err
16751
16809
  });
16752
16810
  }
16811
+ if (handshake && !device) {
16812
+ this.log.error("[pair] E2EE pairing failed: the device could not be registered", {
16813
+ event: "pair.e2ee_registration_failed",
16814
+ ip
16815
+ });
16816
+ json(res, 500, {
16817
+ error: "Pairing failed: this server could not register the device. Scan a fresh pairing code and try again.",
16818
+ code: "E2EE_REGISTRATION_FAILED"
16819
+ });
16820
+ return;
16821
+ }
16753
16822
  let e2eeResponse = null;
16754
- if (handshake) {
16823
+ if (handshake && device) {
16755
16824
  try {
16756
16825
  const { message2 } = writeMessage2(
16757
16826
  handshake,
16758
- Buffer.from(
16759
- JSON.stringify({
16760
- v: E2EE_EXCHANGE_VERSION,
16761
- deviceId: device?.deviceId ?? null,
16762
- serverVersion: getVersion(),
16763
- // Always true on this path: completing a handshake is what pins
16764
- // the device, and design.md §6.3 says nothing a client sends
16765
- // ever clears it.
16766
- e2eeRequired: true
16767
- }),
16768
- "utf-8"
16769
- )
16827
+ // Every result a new client persists or presents as verified, so it
16828
+ // never has to trust the outer, unauthenticated copy of a credential.
16829
+ // The outer response still carries those copies for released builds;
16830
+ // this is what the new client actually reads.
16831
+ encodeE2eeMsg2Payload({
16832
+ deviceId: device.deviceId,
16833
+ deviceToken: device.deviceToken,
16834
+ capabilities: device.capabilities,
16835
+ publicUrl: this.publicUrl,
16836
+ machineName: (0, import_os14.hostname)(),
16837
+ serverVersion: getVersion()
16838
+ })
16770
16839
  );
16771
16840
  e2eeResponse = { v: E2EE_EXCHANGE_VERSION, noise: message2.toString("base64") };
16772
16841
  } catch (err) {