fedipod-server 0.15.0 → 0.17.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.
Files changed (43) hide show
  1. package/README.md +37 -15
  2. package/dist/claims.d.ts +10 -11
  3. package/dist/claims.js +19 -17
  4. package/dist/directory.d.ts +11 -3
  5. package/dist/directory.js +28 -14
  6. package/dist/handler.d.ts +30 -7
  7. package/dist/handler.js +133 -42
  8. package/dist/handler.jsonld +14 -6
  9. package/dist/streaming-handler.js +7 -2
  10. package/lib/client/masto/index.mjs +7 -1
  11. package/lib/client/masto/timelines.mjs +1 -1
  12. package/lib/client/oidc-auth.mjs +5 -3
  13. package/lib/core/publisher/index.mjs +1 -1
  14. package/lib/core/wire.mjs +4 -1
  15. package/lib/device/admin/routes/gateway.mjs +1 -1
  16. package/lib/device/admin/routes/lifecycle.mjs +1 -1
  17. package/lib/device/admin/routes/setup.mjs +20 -1
  18. package/lib/device/admin/surface.mjs +25 -17
  19. package/lib/device/cli/commands/setup.mjs +53 -21
  20. package/lib/device/setup.mjs +105 -25
  21. package/lib/gateway/front-core.mjs +23 -11
  22. package/lib/pod/root.mjs +12 -1
  23. package/lib/server/embed.mjs +23 -6
  24. package/package.json +2 -1
  25. package/run-agent.mjs +1 -1
  26. package/vendor/gate.cjs +5 -2
  27. package/web/admin/setup/index.html +25 -2
  28. package/web/admin/setup/setup.js +30 -1
  29. package/web/app/README.md +2 -2
  30. package/web/app/admin-facade.mjs +1 -1
  31. package/web/app/agent.mjs +3 -3
  32. package/web/app/boot.mjs +2 -2
  33. package/web/app/dist/boot.js +43 -31
  34. package/web/app/dist/boot.js.map +3 -3
  35. package/web/app/dist/sw.js +4 -4
  36. package/web/app/dist/sw.js.map +2 -2
  37. package/web/app/index.html +1 -1
  38. package/web/app/signup.mjs +5 -3
  39. package/web/app/site/admin/setup/index.html +25 -2
  40. package/web/app/site/admin/setup/setup.js +30 -1
  41. package/web/app/site/boot.js +1 -1
  42. package/web/app/site/index.html +1 -1
  43. package/web/app/site/sw.js +3 -3
@@ -170,7 +170,7 @@ a { color: var(--accent); }
170
170
  <input type="url" id="pod" name="pod" autocomplete="url" aria-describedby="pod-hint"
171
171
  placeholder="https://alice.solidcommunity.net/ or https://server.example/alice/">
172
172
  <p class="hint" id="pod-hint">The address of your pod: its own host, or a path on a
173
- shared host.</p>
173
+ suffix-based host.</p>
174
174
  </div>
175
175
 
176
176
  <label for="email">Email (used as pod id)</label>
@@ -14,10 +14,11 @@ import { createAccountWithPod, mintCredential, makeDpopSession, revokeCredential
14
14
  import { generateKeys, wrapKeys } from './keystore.mjs';
15
15
  import { BrowserRemotePod } from './pod-remote.mjs';
16
16
  import * as podState from '../../lib/pod/state.mjs';
17
+ import { resourceExists } from '../../lib/pod/root.mjs';
17
18
  import { cacheOpenedKeys } from './keys-browser.mjs';
18
19
 
19
20
  // The container everything the agent publishes hangs under. New pods made here
20
- // use `fedipod/`; the agent's own default stays `activitypods-js/` for installs
21
+ // use `fedipod/`; new installs default to `fedipod/` too, and older ones keep
21
22
  // that predate this, so those pods are untouched. The name is stored on the
22
23
  // credential and the config, so the agent reads it rather than guessing.
23
24
  export const AP_ROOT = 'fedipod/';
@@ -53,7 +54,7 @@ const progressKey = (a) => [a.issuer, a.mode, a.handle, a.mode === 'new' ? (a.po
53
54
  * `shape` is where the address lives. On the pod, `@handle@yourpod`, with the
54
55
  * gateway as a mail door only — the default. At the gateway, `@handle@front`,
55
56
  * a fronted identity whose documents still live on the pod. A pod on a path of
56
- * a shared host cannot answer WebFinger, so it is fronted whatever was asked.
57
+ * a suffix-based host cannot answer WebFinger, so it is fronted whatever was asked.
57
58
  *
58
59
  * returns: { credential, config, actorUrl, address, keysPublic }
59
60
  */
@@ -102,6 +103,7 @@ export async function signUp(answers, { onStep = () => {}, frontOrigin = null }
102
103
  acct.running('checking your pod');
103
104
  const head = await fetch(brought, { method: 'HEAD' }).catch(() => null);
104
105
  if (!head || head.status >= 400) throw new Error(`the pod at ${brought} did not answer (HTTP ${head?.status || 'no response'})`);
106
+ if (await resourceExists(fetch, actorUrlFor(brought))) throw new Error('The pod already hosts a FediPod account. If you want a second account, put it on a different pod.');
105
107
  prog.pod = brought;
106
108
  acct.skip('using the pod you brought');
107
109
  }
@@ -116,7 +118,7 @@ export async function signUp(answers, { onStep = () => {}, frontOrigin = null }
116
118
  const pathPod = new URL(pod).pathname !== '/';
117
119
  const fronted = pathPod || wantsFront;
118
120
  if (fronted && !frontOrigin) {
119
- throw new Error(`${pod} is a path on a shared host, so its address must live at a gateway, and this page has none.`);
121
+ throw new Error(`${pod} is a suffix-based host, so its address must live at a gateway, and this page has none.`);
120
122
  }
121
123
  if (pathPod && !wantsFront) await assertFrontNameFree(frontOrigin, handle);
122
124
 
@@ -132,9 +132,20 @@ button:disabled { opacity: .5; cursor: default; }
132
132
  <label for="issuer-new">Solid pod provider</label>
133
133
  <select id="issuer-new" name="issuerNew" aria-describedby="issuer-new-hint">
134
134
  <option value="https://solidcommunity.net">solidcommunity.net</option>
135
+ <option value="https://privatedatapod.com">privatedatapod.com</option>
136
+ <option value="https://solid.redpencil.io">redpencil.io</option>
137
+ <option value="https://pods.solidcommunity.au">solidcommunity.au</option>
138
+ <option value="https://solidweb.me">solidweb.me</option>
139
+ <option value="https://teamid.live">teamid.live</option>
140
+ <option value="">Other…</option>
135
141
  </select>
136
- <p class="hint" id="issuer-new-hint">Providers that give each pod its own subdomain, so the
137
- Fediverse address works everywhere.</p>
142
+ <div id="row-issuer-other" hidden>
143
+ <label for="issuerOther">Provider address</label>
144
+ <input type="url" id="issuerOther" name="issuerOther" autocomplete="url" placeholder="https://pods.example.org">
145
+ </div>
146
+ <p class="hint" id="issuer-new-hint">The Community Solid Server providers from
147
+ solidproject.org. A provider that puts pods on paths gives your address at a
148
+ gateway instead.</p>
138
149
  </div>
139
150
 
140
151
  <div id="row-issuer-existing" hidden>
@@ -172,6 +183,18 @@ button:disabled { opacity: .5; cursor: default; }
172
183
  Wrong account or pod?
173
184
  <button type="button" id="form-reenter" title=" Discard the credential and enter the account and pod again">Re-enter credentials</button></p>
174
185
 
186
+ <fieldset id="fs-shape" hidden>
187
+ <legend>Where your address lives</legend>
188
+ <label class="choice"><input type="radio" name="shape" value="pod" checked> On your pod</label>
189
+ <label class="choice"><input type="radio" name="shape" value="front"> At a gateway</label>
190
+ <div id="row-gateway" hidden>
191
+ <label for="gatewayOrigin">Gateway</label>
192
+ <input type="url" id="gatewayOrigin" name="gatewayOrigin" value="https://fedipod.net" autocomplete="url">
193
+ <p class="hint">The gateway answers your address and forwards your mail to your pod.</p>
194
+ </div>
195
+ <p class="hint" id="shape-note" hidden></p>
196
+ </fieldset>
197
+
175
198
  <h2>You will be</h2>
176
199
  <p class="address" id="preview">…</p>
177
200
  <div id="preview-notes"></div>
@@ -93,12 +93,18 @@ function answers() {
93
93
  const f = $('form').elements;
94
94
  const kind = f.kind.value;
95
95
  const mode = state.resumable ? 'existing' : f.mode.value;
96
+ const issuer = mode === 'new'
97
+ ? (f.issuerNew.value || (f.issuerOther ? f.issuerOther.value : '')).trim()
98
+ : f.issuer.value.trim();
96
99
  const a = {
97
100
  kind,
98
101
  mode,
99
102
  handle: f.handle.value.trim(),
100
- issuer: (mode === 'new' ? f.issuerNew.value : f.issuer.value).trim(),
103
+ issuer,
101
104
  email: f.email.value.trim(),
105
+ // Where the address lives; a group cannot front (yet), so it stays 'pod'.
106
+ shape: (kind === 'group') ? 'pod' : (f.shape ? f.shape.value : 'pod'),
107
+ gatewayOrigin: (f.gatewayOrigin ? f.gatewayOrigin.value.trim() : '') || 'https://fedipod.net',
102
108
  };
103
109
  if (mode === 'new') a.podName = f.podName.value.trim() || a.handle;
104
110
  else a.pod = f.pod.value.trim();
@@ -128,6 +134,9 @@ function onEdit() {
128
134
  $('row-pod').hidden = mode === 'new';
129
135
  $('row-issuer-new').hidden = mode !== 'new';
130
136
  $('row-issuer-existing').hidden = mode === 'new';
137
+ const f = $('form').elements;
138
+ if ($('row-issuer-other')) $('row-issuer-other').hidden = !(mode === 'new' && f.issuerNew.value === '');
139
+ if ($('row-gateway')) $('row-gateway').hidden = !(f.shape && f.shape.value === 'front');
131
140
  clearTimeout(editTimer);
132
141
  editTimer = setTimeout(preview, 150);
133
142
  }
@@ -140,6 +149,26 @@ async function preview() {
140
149
  const { json } = await postJson('/setup/check', a);
141
150
  if (!json) return;
142
151
  $('preview').textContent = json.address || '…';
152
+ // The address-shape choice: hidden for a group (a group cannot front yet),
153
+ // locked to the gateway for a pod on a suffix-based host, an open choice
154
+ // for a pod at its own host.
155
+ const f = $('form').elements;
156
+ const shapeFs = $('fs-shape');
157
+ if (shapeFs) {
158
+ const isGroup = a.kind === 'group';
159
+ shapeFs.hidden = isGroup || (a.mode === 'existing' && !a.pod);
160
+ const forced = !!json.forced;
161
+ for (const r of f.shape) r.disabled = forced;
162
+ if (forced) { for (const r of f.shape) r.checked = r.value === 'front'; }
163
+ if ($('row-gateway')) $('row-gateway').hidden = !(f.shape.value === 'front');
164
+ const note = $('shape-note');
165
+ if (note) {
166
+ note.hidden = !forced;
167
+ note.textContent = forced
168
+ ? 'Your pod is on a suffix-based host, so its address lives at the gateway. Your posts, key and data stay on your pod.'
169
+ : '';
170
+ }
171
+ }
143
172
  const notes = $('preview-notes');
144
173
  for (const w of json.warnings || []) {
145
174
  const p = document.createElement('p');
@@ -33321,7 +33321,7 @@ async function signUp(answers, { onStep = () => {
33321
33321
  const pathPod = new URL(pod).pathname !== "/";
33322
33322
  const fronted = pathPod || wantsFront;
33323
33323
  if (fronted && !frontOrigin) {
33324
- throw new Error(`${pod} is a path on a shared host, so its address must live at a gateway, and this page has none.`);
33324
+ throw new Error(`${pod} is a suffix-based host, so its address must live at a gateway, and this page has none.`);
33325
33325
  }
33326
33326
  if (pathPod && !wantsFront) await assertFrontNameFree(frontOrigin, handle);
33327
33327
  const actorUrl = actorUrlFor(pod);
@@ -170,7 +170,7 @@ a { color: var(--accent); }
170
170
  <input type="url" id="pod" name="pod" autocomplete="url" aria-describedby="pod-hint"
171
171
  placeholder="https://alice.solidcommunity.net/ or https://server.example/alice/">
172
172
  <p class="hint" id="pod-hint">The address of your pod: its own host, or a path on a
173
- shared host.</p>
173
+ suffix-based host.</p>
174
174
  </div>
175
175
 
176
176
  <label for="email">Email (used as pod id)</label>
@@ -56507,7 +56507,7 @@ var Publisher = class {
56507
56507
  }
56508
56508
  const pubName = publicHandle(this.config);
56509
56509
  const pubHost = this.config.gateway?.frontActor ? new URL(this.config.gateway.frontActor).host : host;
56510
- this.log(this.config.gateway?.frontActor || webfingerHost(urls.base) ? `profile published: @${pubName}@${pubHost} \u2192 ${urls.actor}` : `profile published \u2192 ${urls.actor} \u2014 NOT discoverable as @${pubName}@${pubHost}: this pod is a path on a shared host, and WebFinger is only answered at a host root`);
56510
+ this.log(this.config.gateway?.frontActor || webfingerHost(urls.base) ? `profile published: @${pubName}@${pubHost} \u2192 ${urls.actor}` : `profile published \u2192 ${urls.actor} \u2014 NOT discoverable as @${pubName}@${pubHost}: this pod is a suffix-based host, and WebFinger is only answered at a host root`);
56511
56511
  return { unreachable, updated };
56512
56512
  }
56513
56513
  // Fires when the document differs from the one last published — including
@@ -68273,7 +68273,7 @@ var AdminFacade = class {
68273
68273
  const aliases = [...cfg.aliases || []];
68274
68274
  if (body.add) {
68275
68275
  if (!webfingerHost(urls.base) && !cfg.gateway?.frontActor) {
68276
- return json2(400, { error: "this pod is a path on a shared host, so other servers could never resolve it as a Move target" });
68276
+ return json2(400, { error: "this pod is a suffix-based host, so other servers could never resolve it as a Move target" });
68277
68277
  }
68278
68278
  const id = await this.resolveActor(body.add);
68279
68279
  if (!id) return json2(400, { error: `could not fetch the old account (${body.add}) \u2014 enter its URL or @user@host, and it must answer` });
@@ -69785,7 +69785,7 @@ var BrowserAgent = class _BrowserAgent {
69785
69785
  publicKeyPem: keys.rsaPublicPem,
69786
69786
  assertionKey: null,
69787
69787
  log: this.log,
69788
- // Who a post names, resolved — the same lookup the installed agent
69788
+ // Who a post names, resolved — the same lookup the DeviceAgent
69789
69789
  // gives its publisher. Without it no mention from the browser ever
69790
69790
  // resolved: a direct message went to nobody, a mention notified no one.
69791
69791
  resolveMention: (h) => resolveHandle(this, h)