@ours.network/messenger-server 1.0.31-nightly.12 → 1.0.31-nightly.14

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/README.md CHANGED
@@ -409,3 +409,7 @@ The older mock walkthrough and its browser tests describe an earlier design stag
409
409
  ### Dedicated notification service
410
410
 
411
411
  Set `OURS_NOTIFICATIONS_ORIGIN` and a Messenger-scoped `OURS_NOTIFICATIONS_PRODUCER_TOKEN` to forward incoming messages/files to [ours-notifications](https://github.com/adapt-toolkit/ours-notifications). Both settings must be provided together. The canonical watcher durably queues notifications even without a local push binding, freezes projected content before its first HTTP attempt, and retries the same payload after outages/restarts. Keep the Messenger state directory across restarts. When configured, new events use the dedicated service; legacy pending push jobs may finish. Existing standalone push endpoints remain for compatibility.
412
+
413
+ Workspace enrollment supports optional `preserveProfile: true` on its protected POST endpoint. This skips human profile creation/replacement (including an absent profile); root checks and the signed binding command remain mandatory. The protected enrollment-identity response advertises `preserveProfile: true` so clients can fail closed against older servers. Omitted/false keeps fresh-host account profile setup.
414
+
415
+ Messenger may run as the Human root or as a permanent identity under it. For the latter, the enrollment-identity response also carries `rootCid`, the root the daemon describes for the bound identity; `POST /api/identity/profile` accepts both. Only a Messenger bound to the root itself signs the workspace binding on `POST /api/workspace/enroll`; for an identity under the root the caller obtains that signature from the root outside Messenger.
@@ -38,7 +38,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
38
38
  var define_MESSENGER_BUILD_INFO_default;
39
39
  var init_define_MESSENGER_BUILD_INFO = __esm({
40
40
  "<define:__MESSENGER_BUILD_INFO__>"() {
41
- define_MESSENGER_BUILD_INFO_default = { name: "@ours.network/messenger-server", version: "1.0.31-nightly.12", sha: "e10c4e8733c42c146f9a97d981bc1f0f504560a3", dirty: true };
41
+ define_MESSENGER_BUILD_INFO_default = { name: "@ours.network/messenger-server", version: "1.0.31-nightly.14", sha: "472ad12fdb461e51398c29e8e224d12caca9a43e", dirty: true };
42
42
  }
43
43
  });
44
44
 
@@ -10,7 +10,7 @@ import {
10
10
  publicEngineError,
11
11
  publicInternalError,
12
12
  reportFailure
13
- } from "./chunk-UBWX7QOC.js";
13
+ } from "./chunk-XNYNQSPB.js";
14
14
 
15
15
  // node_modules/bn.js/lib/bn.js
16
16
  var require_bn = __commonJS({
@@ -20622,6 +20622,15 @@ function inlineBase64(body) {
20622
20622
  }
20623
20623
  return value;
20624
20624
  }
20625
+ function humanIdentity(value) {
20626
+ if (value === null || typeof value !== "object") return void 0;
20627
+ const identity = value;
20628
+ if (identity.temporary !== false || typeof identity.cid !== "string") return void 0;
20629
+ if (identity.isRoot === true) return {};
20630
+ const rootCid = identity.rootCid;
20631
+ if (identity.isRoot !== false || identity.described !== true || typeof identity.roleId !== "string" || !identity.roleId || typeof rootCid !== "string" || !/^[a-f0-9]{64}$/i.test(rootCid) || rootCid.toLowerCase() === identity.cid.toLowerCase()) return void 0;
20632
+ return { rootCid };
20633
+ }
20625
20634
  function publicIdentity(value) {
20626
20635
  if (value === null || typeof value !== "object") return {};
20627
20636
  const identity = value;
@@ -21017,16 +21026,18 @@ var ROUTES = {
21017
21026
  // field on this result.
21018
21027
  "GET /api/identity": async ({ client, deps }) => ({ ...publicIdentity(await client.currentIdentity()), humanProfile: readHumanProfile(deps.config.stateDir, deps.identityCid) }),
21019
21028
  "POST /api/identity/profile": async ({ client, deps, body }) => {
21020
- const identity = await client.currentIdentity();
21021
- if (!identity.isRoot || identity.temporary) throw bad("Human profile requires the root identity");
21029
+ if (!humanIdentity(await client.currentIdentity())) throw bad("Human profile requires the Human root or a permanent identity under it");
21022
21030
  return writeHumanProfile(deps.config.stateDir, deps.identityCid, { name: profileName(body.name), surname: profileName(body.surname) });
21023
21031
  },
21024
- // First-party setup operation: daemon signs the command using this bound root.
21032
+ // Who the person is on this host. A Messenger that runs as the Human root can sign the workspace proof
21033
+ // itself; one that runs as the person's own identity under that root names the root the daemon describes
21034
+ // for it, and the proof is then signed by that root outside Messenger.
21025
21035
  "GET /api/workspace/enrollment-identity": async ({ client }) => {
21026
- const identity = await client.currentIdentity();
21027
- if (!identity.isRoot || identity.temporary) throw bad("Workspace proof requires the bound Human root");
21028
- return { cid: identity.cid };
21036
+ const identity = await client.currentIdentity(), human = humanIdentity(identity);
21037
+ if (!human) throw bad("Workspace proof requires the bound Human root or a permanent identity under it");
21038
+ return human.rootCid === void 0 ? { cid: identity.cid, preserveProfile: true } : { cid: identity.cid, rootCid: human.rootCid, preserveProfile: true };
21029
21039
  },
21040
+ // First-party setup operation: daemon signs the command using this bound root.
21030
21041
  "POST /api/workspace/enroll": async ({ client, deps, body }) => {
21031
21042
  const identity = await client.currentIdentity();
21032
21043
  if (!identity.isRoot || identity.temporary) throw bad("Workspace proof requires the bound Human root");
@@ -21034,15 +21045,16 @@ var ROUTES = {
21034
21045
  if (!/^[A-F0-9]{64}$/.test(serverCid) || !/^[A-Za-z0-9_-]{43}$/.test(str(body, "hostWorkspaceId"))) throw bad("Invalid workspace proof");
21035
21046
  const challenge = body.challenge;
21036
21047
  if (!challenge || !["nonce", "workspaceId", "accountId"].every((k3) => typeof challenge[k3] === "string" && /^[A-Za-z0-9_-]{43}$/.test(String(challenge[k3]))) || !Number.isFinite(challenge.expiresAt) || Number(challenge.expiresAt) <= Date.now() || Number(challenge.expiresAt) > Date.now() + 16 * 6e4) throw bad("Workspace challenge expired or invalid");
21048
+ const preserveProfile = optBool(body, "preserveProfile") === true;
21037
21049
  let humanProfile;
21038
- try {
21050
+ if (!preserveProfile) try {
21039
21051
  humanProfile = { name: profileName(body.name), surname: profileName(body.surname) };
21040
21052
  } catch {
21041
21053
  throw bad("Name and Surname require printable text up to 100 characters");
21042
21054
  }
21043
21055
  const invitation = str(body, "invitation");
21044
21056
  if (invitation.length > 8192) throw bad("Workspace invitation too large");
21045
- writeHumanProfile(deps.config.stateDir, deps.identityCid, humanProfile);
21057
+ if (humanProfile) writeHumanProfile(deps.config.stateDir, deps.identityCid, humanProfile);
21046
21058
  const peer = await client.addContact({ invite: invitation });
21047
21059
  if (peer.cid.toUpperCase() !== serverCid) throw bad("Enrollment server identity mismatch");
21048
21060
  const contactDeadline = Date.now() + 1e4;
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  normalizeBasePath,
8
8
  operatorError,
9
9
  reportFailure
10
- } from "./chunks/chunk-UBWX7QOC.js";
10
+ } from "./chunks/chunk-XNYNQSPB.js";
11
11
 
12
12
  // src/cli.ts
13
13
  init_define_MESSENGER_BUILD_INFO();
@@ -131,7 +131,7 @@ async function main() {
131
131
  }
132
132
  if (process.argv.length !== 3) throw new ConfigurationError("serve does not accept command-line options");
133
133
  const cfg = loadConfig();
134
- const { start } = await import("./chunks/server-YZYMQRLZ.js");
134
+ const { start } = await import("./chunks/server-ML4SVL44.js");
135
135
  const handle = await start(cfg, BUILD_INFO);
136
136
  let closing = false;
137
137
  const shutdown = (signal) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ours.network/messenger-server",
3
- "version": "1.0.31-nightly.12",
3
+ "version": "1.0.31-nightly.14",
4
4
  "description": "Self-hosted Messenger REST/SSE backend for one identity on a shared Ours daemon.",
5
5
  "type": "module",
6
6
  "license": "FSL-1.1-Apache-2.0",
@@ -33,7 +33,7 @@
33
33
  "typecheck": "tsc -p tsconfig.json --noEmit",
34
34
  "test:startup": "node --import tsx tests/startup-readiness.test.mjs",
35
35
  "test:runtime-catalog-candidate": "node --import tsx tests/runtime-command-catalog-reopen-e2e.test.mjs",
36
- "test:offline": "for f in tests/notification-producer.test.mjs tests/base-path.test.mjs tests/build-epoch.test.mjs tests/owner-session.test.mjs tests/v1-runtime.test.mjs tests/typed-commands-core.test.mjs tests/shared-daemon-contract.test.mjs tests/room-envelope-matrix.test.mjs tests/bundle-smoke.test.mjs tests/build-metadata.test.mjs tests/production-security.test.mjs tests/conversation-page.test.mjs tests/media-response.test.mjs tests/html-preview-transform.test.mjs tests/html-preview-response.test.mjs tests/push-store.test.mjs tests/push-store-hardening.test.mjs tests/push-delivery.test.mjs tests/push-queue-saturation.test.mjs tests/events.test.mjs tests/watch-cursor.test.mjs tests/watch-reconcile.test.mjs tests/introduction-send-guard.test.mjs tests/sse-e2e.test.mjs tests/presence.test.mjs tests/static-hosting.test.mjs tests/room-preview.test.mjs; do echo \"# $f\"; node --import tsx \"$f\" || exit 1; done",
36
+ "test:offline": "for f in tests/workspace-profile-preservation.test.mjs tests/notification-producer.test.mjs tests/base-path.test.mjs tests/build-epoch.test.mjs tests/owner-session.test.mjs tests/v1-runtime.test.mjs tests/typed-commands-core.test.mjs tests/shared-daemon-contract.test.mjs tests/room-envelope-matrix.test.mjs tests/bundle-smoke.test.mjs tests/build-metadata.test.mjs tests/production-security.test.mjs tests/conversation-page.test.mjs tests/media-response.test.mjs tests/html-preview-transform.test.mjs tests/html-preview-response.test.mjs tests/push-store.test.mjs tests/push-store-hardening.test.mjs tests/push-delivery.test.mjs tests/push-queue-saturation.test.mjs tests/events.test.mjs tests/watch-cursor.test.mjs tests/watch-reconcile.test.mjs tests/introduction-send-guard.test.mjs tests/sse-e2e.test.mjs tests/presence.test.mjs tests/static-hosting.test.mjs tests/room-preview.test.mjs; do echo \"# $f\"; node --import tsx \"$f\" || exit 1; done",
37
37
  "test:loopback": "for f in tests/shared-daemon-e2e.test.mjs tests/typed-command-loopback-e2e.test.mjs tests/introduction-send.test.mjs tests/rest-e2e.test.mjs; do echo \"# $f\"; node --import tsx \"$f\" || exit 1; done",
38
38
  "test": "npm run build && npm run test:startup && npm run test:offline && npm run test:loopback"
39
39
  },