fedipod-server 0.17.0 → 0.18.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/lib/client/c2s.mjs +95 -61
- package/lib/core/contexts/anno.json +126 -0
- package/lib/core/contexts/index.mjs +4 -0
- package/lib/core/contexts/map.json +2 -1
- package/lib/core/intake/index.mjs +32 -5
- package/lib/core/publisher/index.mjs +15 -1
- package/lib/core/publisher/notes.mjs +84 -2
- package/lib/core/publisher/questions.mjs +1 -0
- package/lib/core/publisher/restore.mjs +27 -2
- package/lib/core/social.mjs +1 -0
- package/lib/core/store.mjs +4 -0
- package/lib/core/wire.mjs +16 -12
- package/lib/gateway/front-core.mjs +43 -1
- package/lib/gateway/gateway-core.mjs +40 -0
- package/lib/pod/actor.mjs +2 -2
- package/lib/pod/transport.mjs +9 -3
- package/package.json +1 -1
- package/run-agent.mjs +9 -0
- package/web/admin/index.html +2 -0
- package/web/admin/upkeep.js +9 -1
- package/web/app/README.md +1 -1
- package/web/app/agent.mjs +10 -0
- package/web/app/boot.mjs +49 -10
- package/web/app/dist/boot.js +47 -7
- package/web/app/dist/boot.js.map +2 -2
- package/web/app/dist/sw.js +1325 -661
- package/web/app/dist/sw.js.map +4 -4
- package/web/app/index.html +16 -6
- package/web/app/site/admin/index.html +2 -0
- package/web/app/site/admin/upkeep.js +9 -1
- package/web/app/site/boot.js +89 -37
- package/web/app/site/index.html +16 -6
- package/web/app/site/sw.js +1326 -662
- package/web/front/#new-account.html# +0 -43
- package/web/front/new-account.html~ +0 -50
package/web/app/index.html
CHANGED
|
@@ -110,20 +110,30 @@ a { color: var(--accent); }
|
|
|
110
110
|
</section>
|
|
111
111
|
|
|
112
112
|
<!-- Shown only on a browser that has signed in but has never opened the signing
|
|
113
|
-
key. The key is kept on the pod encrypted under the
|
|
114
|
-
the pod's host cannot sign as you; a browser opens it once and remembers.
|
|
113
|
+
key. The key is kept on the pod encrypted under the sign-up password, so
|
|
114
|
+
the pod's host cannot sign as you; a browser opens it once and remembers.
|
|
115
|
+
The second block is for someone who no longer has that password: a new
|
|
116
|
+
key, locked under the password they use now. -->
|
|
115
117
|
<section id="unlock" hidden>
|
|
116
118
|
<h2>Unlock your signing key</h2>
|
|
117
|
-
<p>Your signing key is stored on your pod, locked under
|
|
118
|
-
so nobody who can read your pod — your pod's host included
|
|
119
|
-
This browser has not opened it yet.</p>
|
|
119
|
+
<p>Your signing key is stored on your pod, locked under the password you gave
|
|
120
|
+
when you signed up, so nobody who can read your pod — your pod's host included
|
|
121
|
+
— can post as you. This browser has not opened it yet.</p>
|
|
120
122
|
<p class="row">
|
|
121
123
|
<input type="password" id="unlock-password" autocomplete="current-password"
|
|
122
|
-
placeholder="Your
|
|
124
|
+
placeholder="Your password" aria-label="Your password">
|
|
123
125
|
<button type="button" class="primary" id="unlock-go">unlock</button>
|
|
124
126
|
</p>
|
|
125
127
|
<p class="err" id="unlock-error" role="alert"></p>
|
|
126
128
|
<p class="hint">This browser only — it is not sent anywhere.</p>
|
|
129
|
+
<h2>Changed your pod password, or lost the old one?</h2>
|
|
130
|
+
<p>Make a new signing key, locked under the password you use now. Your account,
|
|
131
|
+
address, posts and followers stay as they are; other servers learn the new key
|
|
132
|
+
the next time they hear from you.</p>
|
|
133
|
+
<p><button type="button" id="unlock-newkey">make a new signing key</button></p>
|
|
134
|
+
<p id="unlock-newkey-confirm" hidden>This replaces the key on your pod. Type the
|
|
135
|
+
password you use now in the field above, then
|
|
136
|
+
<button type="button" id="unlock-newkey-go">make the new key</button></p>
|
|
127
137
|
</section>
|
|
128
138
|
|
|
129
139
|
|
|
@@ -277,6 +277,8 @@ pre { overflow-x: auto; background: #0001; padding: .6rem; border-radius: .3rem;
|
|
|
277
277
|
|
|
278
278
|
<section id="pane-identity" hidden>
|
|
279
279
|
<dl id="facts"></dl>
|
|
280
|
+
<!-- State documents the agent could not read on its last load, if any. -->
|
|
281
|
+
<p class="err" id="state-skipped" role="alert" hidden></p>
|
|
280
282
|
<!-- Declared here, and render() puts these on the kind row — the row they
|
|
281
283
|
belong to is generated, so this is the only place they can be written
|
|
282
284
|
down. Each reads as the setting it would change, so what it shows IS the
|
|
@@ -145,8 +145,16 @@ const INBOX_PROMPT_AT = 500;
|
|
|
145
145
|
let dismissed = false;
|
|
146
146
|
|
|
147
147
|
async function renderInbox() {
|
|
148
|
-
if (dismissed) return;
|
|
149
148
|
const { json: st } = await api('/status');
|
|
149
|
+
// A state document the last load could not read is a timeline or a contact
|
|
150
|
+
// list quietly missing; the page says which, under the facts.
|
|
151
|
+
const skipped = st?.stateSkipped || [];
|
|
152
|
+
const line = $('state-skipped');
|
|
153
|
+
line.hidden = !skipped.length;
|
|
154
|
+
line.textContent = skipped.length
|
|
155
|
+
? `${skipped.length} state document${skipped.length === 1 ? '' : 's'} could not be read on the last load: ${skipped.join(', ')}.`
|
|
156
|
+
: '';
|
|
157
|
+
if (dismissed) return;
|
|
150
158
|
const box = st?.inbox;
|
|
151
159
|
const panel = $('pane-inbox');
|
|
152
160
|
if (!box || box.count < INBOX_PROMPT_AT) { panel.hidden = true; return; }
|
package/web/app/site/boot.js
CHANGED
|
@@ -33075,7 +33075,7 @@ var PodTransport = class {
|
|
|
33075
33075
|
* says all of it. The parsed graph must mention the WebID before anything is
|
|
33076
33076
|
* written back — an empty or foreign body must never become the new profile.
|
|
33077
33077
|
*/
|
|
33078
|
-
async linkAccountInProfile({ actorUrl, accountName, kind = "person" }) {
|
|
33078
|
+
async linkAccountInProfile({ actorUrl, accountName, kind = "person", outbox = null }) {
|
|
33079
33079
|
const docUrl = this.webId.split("#")[0];
|
|
33080
33080
|
const res = await this.fetch(docUrl, { headers: { accept: "text/turtle" } });
|
|
33081
33081
|
if (res.status >= 400) throw new Error(`[${this.label}] GET ${docUrl} \u2192 ${res.status}`);
|
|
@@ -33091,10 +33091,16 @@ var PodTransport = class {
|
|
|
33091
33091
|
[me, FOAF("account"), actor],
|
|
33092
33092
|
[actor, RDF3("type"), FOAF("OnlineAccount")],
|
|
33093
33093
|
[actor, RDF3("type"), kind === "group" ? AS("Group") : AS("Person")],
|
|
33094
|
-
[actor, FOAF("accountName"), literal2(accountName)]
|
|
33094
|
+
[actor, FOAF("accountName"), literal2(accountName)],
|
|
33095
|
+
// Where a Solid client posts on this person's behalf (`as:outbox` on the
|
|
33096
|
+
// WebID is what dokieli reads); only where a door exists to take it.
|
|
33097
|
+
...outbox ? [[me, AS("outbox"), namedNode2(outbox)]] : []
|
|
33095
33098
|
];
|
|
33096
33099
|
const missing = wanted.filter(([s, p, o]) => !g.holds(s, p, o, doc));
|
|
33097
|
-
const stale =
|
|
33100
|
+
const stale = [
|
|
33101
|
+
...g.statementsMatching(actor, FOAF("accountName"), null, doc).filter((st2) => st2.object.value !== accountName),
|
|
33102
|
+
...outbox ? g.statementsMatching(me, AS("outbox"), null, doc).filter((st2) => st2.object.value !== outbox) : []
|
|
33103
|
+
];
|
|
33098
33104
|
if (!missing.length && !stale.length) return false;
|
|
33099
33105
|
const deletes = stale.map((st2) => [st2.subject, st2.predicate, st2.object]);
|
|
33100
33106
|
if (await this.patchDocument(docUrl, missing, deletes)) return true;
|
|
@@ -33202,12 +33208,54 @@ var BrowserRemotePod = class extends PodTransport {
|
|
|
33202
33208
|
// lib/pod/state.mjs
|
|
33203
33209
|
var readWrappedKeys = (pod, urls) => pod.getJson(urls.state + "keys.json");
|
|
33204
33210
|
var readConfig = (pod, urls) => pod.getJson(urls.state + "config.json");
|
|
33211
|
+
var writeWrappedKeys = (pod, urls, envelope) => pod.putJson(urls.state + "keys.json", envelope, "application/json");
|
|
33205
33212
|
var writeConfig = (pod, urls, config) => pod.putJson(urls.state + "config.json", config, "application/json");
|
|
33206
33213
|
async function provisionKey(pod, { stateUrl, keysUrl, envelope }) {
|
|
33207
33214
|
await pod.setAcl(stateUrl, []);
|
|
33208
33215
|
await pod.putJson(keysUrl, envelope, "application/json");
|
|
33209
33216
|
}
|
|
33210
33217
|
|
|
33218
|
+
// lib/pod/root.mjs
|
|
33219
|
+
var OWNER_LOOKUP_MS = 5e3;
|
|
33220
|
+
var PUBLIC_DOC_MAX_BYTES = 1024 * 1024;
|
|
33221
|
+
async function podLayout(fetchImpl, providerOrigin, { timeoutMs = OWNER_LOOKUP_MS } = {}) {
|
|
33222
|
+
let origin;
|
|
33223
|
+
try {
|
|
33224
|
+
origin = new URL(providerOrigin).origin;
|
|
33225
|
+
} catch {
|
|
33226
|
+
return null;
|
|
33227
|
+
}
|
|
33228
|
+
let res;
|
|
33229
|
+
try {
|
|
33230
|
+
res = await fetchImpl(
|
|
33231
|
+
`${origin}/.well-known/solid`,
|
|
33232
|
+
{ headers: { accept: "text/turtle" }, signal: AbortSignal.timeout(timeoutMs) }
|
|
33233
|
+
);
|
|
33234
|
+
} catch {
|
|
33235
|
+
return null;
|
|
33236
|
+
}
|
|
33237
|
+
if (res.status === 501) return "host";
|
|
33238
|
+
if (res.status !== 200) return null;
|
|
33239
|
+
let body = "";
|
|
33240
|
+
try {
|
|
33241
|
+
body = await readCapped(res, 64 * 1024);
|
|
33242
|
+
} catch {
|
|
33243
|
+
return null;
|
|
33244
|
+
}
|
|
33245
|
+
return /ns\/pim\/space#Storage|pim:Storage/u.test(body) ? "path" : null;
|
|
33246
|
+
}
|
|
33247
|
+
async function resourceExists(fetchImpl, url, { timeoutMs = OWNER_LOOKUP_MS } = {}) {
|
|
33248
|
+
try {
|
|
33249
|
+
const res = await fetchImpl(
|
|
33250
|
+
url,
|
|
33251
|
+
{ headers: { accept: "application/activity+json" }, signal: AbortSignal.timeout(timeoutMs) }
|
|
33252
|
+
);
|
|
33253
|
+
return !!res && res.status === 200;
|
|
33254
|
+
} catch {
|
|
33255
|
+
return false;
|
|
33256
|
+
}
|
|
33257
|
+
}
|
|
33258
|
+
|
|
33211
33259
|
// web/app/idb-kv.mjs
|
|
33212
33260
|
var DB = "fedipod-accounts";
|
|
33213
33261
|
function open() {
|
|
@@ -33310,6 +33358,7 @@ async function signUp(answers, { onStep = () => {
|
|
|
33310
33358
|
acct.running("checking your pod");
|
|
33311
33359
|
const head = await fetch(brought, { method: "HEAD" }).catch(() => null);
|
|
33312
33360
|
if (!head || head.status >= 400) throw new Error(`the pod at ${brought} did not answer (HTTP ${head?.status || "no response"})`);
|
|
33361
|
+
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.");
|
|
33313
33362
|
prog.pod = brought;
|
|
33314
33363
|
acct.skip("using the pod you brought");
|
|
33315
33364
|
}
|
|
@@ -33450,36 +33499,6 @@ function podBaseOfWebId(webId) {
|
|
|
33450
33499
|
return `${u.origin}${dir.endsWith("/") ? dir : dir + "/"}`;
|
|
33451
33500
|
}
|
|
33452
33501
|
|
|
33453
|
-
// lib/pod/root.mjs
|
|
33454
|
-
var OWNER_LOOKUP_MS = 5e3;
|
|
33455
|
-
var PUBLIC_DOC_MAX_BYTES = 1024 * 1024;
|
|
33456
|
-
async function podLayout(fetchImpl, providerOrigin, { timeoutMs = OWNER_LOOKUP_MS } = {}) {
|
|
33457
|
-
let origin;
|
|
33458
|
-
try {
|
|
33459
|
-
origin = new URL(providerOrigin).origin;
|
|
33460
|
-
} catch {
|
|
33461
|
-
return null;
|
|
33462
|
-
}
|
|
33463
|
-
let res;
|
|
33464
|
-
try {
|
|
33465
|
-
res = await fetchImpl(
|
|
33466
|
-
`${origin}/.well-known/solid`,
|
|
33467
|
-
{ headers: { accept: "text/turtle" }, signal: AbortSignal.timeout(timeoutMs) }
|
|
33468
|
-
);
|
|
33469
|
-
} catch {
|
|
33470
|
-
return null;
|
|
33471
|
-
}
|
|
33472
|
-
if (res.status === 501) return "host";
|
|
33473
|
-
if (res.status !== 200) return null;
|
|
33474
|
-
let body = "";
|
|
33475
|
-
try {
|
|
33476
|
-
body = await readCapped(res, 64 * 1024);
|
|
33477
|
-
} catch {
|
|
33478
|
-
return null;
|
|
33479
|
-
}
|
|
33480
|
-
return /ns\/pim\/space#Storage|pim:Storage/u.test(body) ? "path" : null;
|
|
33481
|
-
}
|
|
33482
|
-
|
|
33483
33502
|
// web/app/oidc-session.mjs
|
|
33484
33503
|
var DB2 = "fedipod-oidc";
|
|
33485
33504
|
var STORE = "session";
|
|
@@ -33725,8 +33744,7 @@ async function bootWorker({ reset = false } = {}) {
|
|
|
33725
33744
|
worker.postMessage({ type: "boot", frontOrigin: location.origin });
|
|
33726
33745
|
await booted;
|
|
33727
33746
|
}
|
|
33728
|
-
|
|
33729
|
-
if (!password) throw new Error("Enter your account password.");
|
|
33747
|
+
async function readAccountState() {
|
|
33730
33748
|
const session = await getSession();
|
|
33731
33749
|
if (!session) throw new Error("Sign in first.");
|
|
33732
33750
|
const podFromWebId = podBaseOfWebId(session.webId);
|
|
@@ -33738,13 +33756,28 @@ window.fedipodUnlock = async (password) => {
|
|
|
33738
33756
|
readConfig(remote, urls),
|
|
33739
33757
|
readWrappedKeys(remote, urls)
|
|
33740
33758
|
]);
|
|
33741
|
-
if (!cfg
|
|
33759
|
+
if (!cfg) throw new Error(`could not read this account's config under ${state}`);
|
|
33760
|
+
const actorUrl = `${cfg.remotePod}${cfg.root || AP_ROOT}ap/actor`;
|
|
33761
|
+
return { remote, urls, cfg, doc, actorUrl };
|
|
33762
|
+
}
|
|
33763
|
+
window.fedipodUnlock = async (password) => {
|
|
33764
|
+
if (!password) throw new Error("Enter your password.");
|
|
33765
|
+
const { doc, actorUrl } = await readAccountState();
|
|
33766
|
+
if (!doc) throw new Error("could not read this account's key on the pod");
|
|
33742
33767
|
if (!isKeyEnvelope(doc)) throw new Error("this account's key is not locked \u2014 nothing to unlock");
|
|
33743
33768
|
const rec = await unwrapKeys(doc, password);
|
|
33744
|
-
const actorUrl = `${cfg.remotePod}${cfg.root || AP_ROOT}ap/actor`;
|
|
33745
33769
|
await cacheOpenedKeys(actorUrl, rec);
|
|
33746
33770
|
await bootWorker();
|
|
33747
33771
|
};
|
|
33772
|
+
window.fedipodNewKey = async (password) => {
|
|
33773
|
+
if (!password) throw new Error("Enter the password you use for your pod now.");
|
|
33774
|
+
const { remote, urls, cfg, actorUrl } = await readAccountState();
|
|
33775
|
+
const keys = await generateKeys();
|
|
33776
|
+
keys.mintedFor = cfg.gateway?.frontActor || actorUrl;
|
|
33777
|
+
await writeWrappedKeys(remote, urls, await wrapKeys(keys, password));
|
|
33778
|
+
await cacheOpenedKeys(actorUrl, keys);
|
|
33779
|
+
await bootWorker();
|
|
33780
|
+
};
|
|
33748
33781
|
window.fedipodSignup = async ({ onStep, ...answers }) => {
|
|
33749
33782
|
await signUp(answers, { onStep, frontOrigin: location.origin });
|
|
33750
33783
|
const { authorizationUrl } = await beginLogin({ issuer: answers.issuer, redirectUri: REDIRECT });
|
|
@@ -33849,6 +33882,25 @@ if (typeof document !== "undefined") (async () => {
|
|
|
33849
33882
|
$("unlock-password")?.addEventListener("keydown", (e) => {
|
|
33850
33883
|
if (e.key === "Enter") doUnlock();
|
|
33851
33884
|
});
|
|
33885
|
+
$("unlock-newkey")?.addEventListener("click", () => {
|
|
33886
|
+
$("unlock-newkey-confirm").hidden = false;
|
|
33887
|
+
$("unlock-password").focus();
|
|
33888
|
+
});
|
|
33889
|
+
const doNewKey = async () => {
|
|
33890
|
+
$("unlock-error").textContent = "";
|
|
33891
|
+
const btn = $("unlock-newkey-go");
|
|
33892
|
+
btn.disabled = true;
|
|
33893
|
+
try {
|
|
33894
|
+
await window.fedipodNewKey($("unlock-password").value);
|
|
33895
|
+
$("unlock-password").value = "";
|
|
33896
|
+
location.href = "/admin/client/";
|
|
33897
|
+
} catch (err) {
|
|
33898
|
+
$("unlock-error").textContent = err.message || String(err);
|
|
33899
|
+
btn.disabled = false;
|
|
33900
|
+
$("unlock-password").select();
|
|
33901
|
+
}
|
|
33902
|
+
};
|
|
33903
|
+
$("unlock-newkey-go")?.addEventListener("click", doNewKey);
|
|
33852
33904
|
if (params.has("signout") || params.has("add")) {
|
|
33853
33905
|
if (params.has("signout")) {
|
|
33854
33906
|
try {
|
package/web/app/site/index.html
CHANGED
|
@@ -110,20 +110,30 @@ a { color: var(--accent); }
|
|
|
110
110
|
</section>
|
|
111
111
|
|
|
112
112
|
<!-- Shown only on a browser that has signed in but has never opened the signing
|
|
113
|
-
key. The key is kept on the pod encrypted under the
|
|
114
|
-
the pod's host cannot sign as you; a browser opens it once and remembers.
|
|
113
|
+
key. The key is kept on the pod encrypted under the sign-up password, so
|
|
114
|
+
the pod's host cannot sign as you; a browser opens it once and remembers.
|
|
115
|
+
The second block is for someone who no longer has that password: a new
|
|
116
|
+
key, locked under the password they use now. -->
|
|
115
117
|
<section id="unlock" hidden>
|
|
116
118
|
<h2>Unlock your signing key</h2>
|
|
117
|
-
<p>Your signing key is stored on your pod, locked under
|
|
118
|
-
so nobody who can read your pod — your pod's host included
|
|
119
|
-
This browser has not opened it yet.</p>
|
|
119
|
+
<p>Your signing key is stored on your pod, locked under the password you gave
|
|
120
|
+
when you signed up, so nobody who can read your pod — your pod's host included
|
|
121
|
+
— can post as you. This browser has not opened it yet.</p>
|
|
120
122
|
<p class="row">
|
|
121
123
|
<input type="password" id="unlock-password" autocomplete="current-password"
|
|
122
|
-
placeholder="Your
|
|
124
|
+
placeholder="Your password" aria-label="Your password">
|
|
123
125
|
<button type="button" class="primary" id="unlock-go">unlock</button>
|
|
124
126
|
</p>
|
|
125
127
|
<p class="err" id="unlock-error" role="alert"></p>
|
|
126
128
|
<p class="hint">This browser only — it is not sent anywhere.</p>
|
|
129
|
+
<h2>Changed your pod password, or lost the old one?</h2>
|
|
130
|
+
<p>Make a new signing key, locked under the password you use now. Your account,
|
|
131
|
+
address, posts and followers stay as they are; other servers learn the new key
|
|
132
|
+
the next time they hear from you.</p>
|
|
133
|
+
<p><button type="button" id="unlock-newkey">make a new signing key</button></p>
|
|
134
|
+
<p id="unlock-newkey-confirm" hidden>This replaces the key on your pod. Type the
|
|
135
|
+
password you use now in the field above, then
|
|
136
|
+
<button type="button" id="unlock-newkey-go">make the new key</button></p>
|
|
127
137
|
</section>
|
|
128
138
|
|
|
129
139
|
|