fedipod 1.27.1 → 1.27.3
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/core/publisher/collections.mjs +6 -1
- package/lib/core/publisher/index.mjs +1 -0
- package/package.json +1 -1
- package/run-agent.mjs +14 -0
- package/web/app/dist/sw.js +58 -7
- package/web/app/dist/sw.js.map +2 -2
- package/web/app/site/admin/client/index.html +2 -2
- package/web/app/site/admin/client-phanpy/index.html +2 -2
- package/web/app/site/admin/index.html +2 -2
- package/web/app/site/admin/setup/index.html +2 -2
- package/web/app/site/app/compose/index.html +2 -2
- package/web/app/site/app/index.html +2 -2
- package/web/app/site/authorize_interaction.html +2 -2
- package/web/app/site/bb/index.html +2 -2
- package/web/app/site/build.json +1 -1
- package/web/app/site/index.html +2 -2
- package/web/app/site/sw.js +58 -7
|
@@ -15,7 +15,7 @@ import * as podPrivate from '../../pod/private.mjs';
|
|
|
15
15
|
// still gets everything, so a missed call site degrades to the old cost rather
|
|
16
16
|
// than silently publishing nothing.
|
|
17
17
|
export const ALL_COLLECTIONS = { followers: true, following: true, outbox: true, acls: true,
|
|
18
|
-
pending: true, blocked: true };
|
|
18
|
+
pending: true, blocked: true, featured: true };
|
|
19
19
|
|
|
20
20
|
// Publish the collections a change actually TOUCHED.
|
|
21
21
|
//
|
|
@@ -152,6 +152,11 @@ export async function publishCollections(publisher, which = ALL_COLLECTIONS) {
|
|
|
152
152
|
}
|
|
153
153
|
if (which.pending) await publisher.publishPending();
|
|
154
154
|
if (which.blocked) await publisher.publishBlocked();
|
|
155
|
+
// The pinned posts, written even when there are none. The actor names this
|
|
156
|
+
// collection, and a server showing the profile reads it whether or not
|
|
157
|
+
// anything was ever pinned; with no document there the pod refused every
|
|
158
|
+
// reader, a refusal nothing caches, and every server asked again.
|
|
159
|
+
if (which.featured) await publisher.publishFeatured();
|
|
155
160
|
if (which.outbox) {
|
|
156
161
|
const outbox = publisher.store.read('outbox.json', []);
|
|
157
162
|
// Reconcile reads every published page, which is the expensive part of a
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fedipod",
|
|
3
|
-
"version": "1.27.
|
|
3
|
+
"version": "1.27.3",
|
|
4
4
|
"description": "Standalone single-actor ActivityPub agent whose wire face, RDF truth and state all live on a Solid pod (CSS). Bundles a Phanpy UI and a Mastodon client-API facade.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/run-agent.mjs
CHANGED
|
@@ -497,6 +497,18 @@ export class Agent {
|
|
|
497
497
|
return true;
|
|
498
498
|
}
|
|
499
499
|
|
|
500
|
+
// The featured collection was once written only on the first pin, so an
|
|
501
|
+
// account from before then has none, and every server that shows the
|
|
502
|
+
// profile is refused and asks again. One authenticated GET; the empty
|
|
503
|
+
// collection goes up when the pod lacks it. True when it had to.
|
|
504
|
+
async ensureFeaturedPublished() {
|
|
505
|
+
const doc = await this.remote.getJson(this.urls.featured);
|
|
506
|
+
if (doc?.id) return false;
|
|
507
|
+
this.log('featured collection missing from the pod — publishing it');
|
|
508
|
+
await this.publisher.publishFeatured();
|
|
509
|
+
return true;
|
|
510
|
+
}
|
|
511
|
+
|
|
500
512
|
// Read-only mode: refresh the state cache periodically, and take over the
|
|
501
513
|
// moment the active agent's lease frees.
|
|
502
514
|
startViewer() {
|
|
@@ -588,6 +600,8 @@ export class Agent {
|
|
|
588
600
|
if (repair) {
|
|
589
601
|
this.ensureActorPublished()
|
|
590
602
|
.catch(e => this.log(`actor check failed: ${e.message}`));
|
|
603
|
+
this.ensureFeaturedPublished()
|
|
604
|
+
.catch(e => this.log(`featured check failed: ${e.message}`));
|
|
591
605
|
// The human page, rewritten only when what it shows has changed: one
|
|
592
606
|
// local digest compare, and a write the first time after an upgrade.
|
|
593
607
|
this.publisher.publishProfilePage()
|
package/web/app/dist/sw.js
CHANGED
|
@@ -45643,7 +45643,8 @@ var ALL_COLLECTIONS = {
|
|
|
45643
45643
|
outbox: true,
|
|
45644
45644
|
acls: true,
|
|
45645
45645
|
pending: true,
|
|
45646
|
-
blocked: true
|
|
45646
|
+
blocked: true,
|
|
45647
|
+
featured: true
|
|
45647
45648
|
};
|
|
45648
45649
|
async function publishOutbox(publisher, outbox, { acls = false, force = false } = {}) {
|
|
45649
45650
|
const { urls } = publisher;
|
|
@@ -45742,6 +45743,7 @@ async function publishCollections(publisher, which = ALL_COLLECTIONS) {
|
|
|
45742
45743
|
}
|
|
45743
45744
|
if (which.pending) await publisher.publishPending();
|
|
45744
45745
|
if (which.blocked) await publisher.publishBlocked();
|
|
45746
|
+
if (which.featured) await publisher.publishFeatured();
|
|
45745
45747
|
if (which.outbox) {
|
|
45746
45748
|
const outbox = publisher.store.read("outbox.json", []);
|
|
45747
45749
|
const known2 = publisher.store.read("published.json", {}).outboxIndex;
|
|
@@ -57345,6 +57347,7 @@ var Publisher = class {
|
|
|
57345
57347
|
["notes", urls.notes],
|
|
57346
57348
|
["followers", urls.followers],
|
|
57347
57349
|
["following", urls.following],
|
|
57350
|
+
["featured", urls.featured],
|
|
57348
57351
|
["outbox", urls.outbox]
|
|
57349
57352
|
];
|
|
57350
57353
|
const unreachable = [];
|
|
@@ -65852,6 +65855,39 @@ function checkPassword(rec, password) {
|
|
|
65852
65855
|
}
|
|
65853
65856
|
var escapeHtml = (s) => String(s).replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
65854
65857
|
var parseRedirects = (v) => (Array.isArray(v) ? v : String(v || "").split(/\s+/)).map((s) => s.trim()).filter(Boolean);
|
|
65858
|
+
function sendPodSigninPage(res, client, webId, mount = "") {
|
|
65859
|
+
let asking = "";
|
|
65860
|
+
if (client && (client.name || client.redirect)) {
|
|
65861
|
+
let where = "";
|
|
65862
|
+
try {
|
|
65863
|
+
where = client.redirect ? new URL(client.redirect).host : "";
|
|
65864
|
+
} catch {
|
|
65865
|
+
}
|
|
65866
|
+
const who = client.name ? escapeHtml(client.name) : where ? escapeHtml(where) : "A client";
|
|
65867
|
+
asking = `<p><strong>${who}</strong> is asking to use your account${where ? `, and will be sent back to <code>${escapeHtml(where)}</code>` : ""}.</p>`;
|
|
65868
|
+
}
|
|
65869
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-cache" });
|
|
65870
|
+
res.end(`<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
|
|
65871
|
+
<title>Sign in with your pod</title>
|
|
65872
|
+
<style>:root{color-scheme:light dark;--heading:#1a4f8a;--btn:#3d6b35;--line:#9a9a9a}
|
|
65873
|
+
body{font:20px Arial,Helvetica,sans-serif;max-width:28rem;margin:12vh auto;padding:0 1rem}
|
|
65874
|
+
h1{color:var(--heading);font-size:1.5rem}
|
|
65875
|
+
code{word-break:break-all;background:rgba(120,120,120,.12);padding:.05em .35em;border-radius:4px}
|
|
65876
|
+
button{font:inherit;padding:.6rem 1.1rem;background:var(--btn);color:#fff;border:none;border-radius:9px;cursor:pointer}
|
|
65877
|
+
button[hidden]{display:none}
|
|
65878
|
+
#signin-status{min-height:1.5em;padding:.6rem .8rem;border:1px solid var(--line);border-radius:8px;margin-top:1rem}
|
|
65879
|
+
@media (prefers-color-scheme:dark){:root{--heading:#7fb3e8;--btn:#5a8a4f}}</style></head><body>
|
|
65880
|
+
<main>
|
|
65881
|
+
<h1>Sign in with your pod</h1>
|
|
65882
|
+
${asking}
|
|
65883
|
+
<p>This account belongs to <a id="webid" href="${escapeHtml(webId)}">${escapeHtml(webId)}</a>. Sign in at that pod to allow it.</p>
|
|
65884
|
+
<p><button type="button" id="signin" hidden>Sign in with your pod</button></p>
|
|
65885
|
+
<p id="signin-status" role="status" aria-live="polite"></p>
|
|
65886
|
+
</main>
|
|
65887
|
+
<script type="module" src="${escapeHtml(mount)}/oauth/session/signin.mjs"><\/script>
|
|
65888
|
+
</body></html>`);
|
|
65889
|
+
return true;
|
|
65890
|
+
}
|
|
65855
65891
|
function sendLoginForm(res, params, error2 = "", client = null, status2 = null, headers = {}) {
|
|
65856
65892
|
const hidden = [...params.entries()].filter(([k]) => k !== "password").map(([k, v]) => `<input type="hidden" name="${escapeHtml(k)}" value="${escapeHtml(v)}">`).join("\n");
|
|
65857
65893
|
let asking = "<p>Enter the agent password to authorize this client.</p>";
|
|
@@ -66120,6 +66156,16 @@ async function handle(api, ctx) {
|
|
|
66120
66156
|
params = new URLSearchParams(body);
|
|
66121
66157
|
}
|
|
66122
66158
|
const redirect = params.get("redirect_uri") || "";
|
|
66159
|
+
let proved = false;
|
|
66160
|
+
if (req.method === "POST" && req.headers.authorization && req.headers.dpop && api.podAuth) {
|
|
66161
|
+
const proof = await api.podAuth(req, pathname);
|
|
66162
|
+
if (!proof.ok) return send(proof.status || 401, { error: proof.error });
|
|
66163
|
+
proved = true;
|
|
66164
|
+
}
|
|
66165
|
+
const ownerWebId = api.ownerWebId?.() || null;
|
|
66166
|
+
if (req.method === "GET" && !params.get("client_id") && params.get("code") && params.get("state") && ownerWebId) {
|
|
66167
|
+
return sendPodSigninPage(res, null, ownerWebId, api.mount || "");
|
|
66168
|
+
}
|
|
66123
66169
|
const app = api.findApp(params.get("client_id") || "");
|
|
66124
66170
|
const doc = app ? null : await api.resolveClientDocument(params.get("client_id") || "");
|
|
66125
66171
|
const external = !!app || !!doc;
|
|
@@ -66127,13 +66173,14 @@ async function handle(api, ctx) {
|
|
|
66127
66173
|
api.log(`authorize refused: cross-site navigation to the mint from ${req.headers.referer || "nowhere"}`);
|
|
66128
66174
|
return send(403, { error: "a cross-site navigation may not authorize a client" });
|
|
66129
66175
|
}
|
|
66130
|
-
|
|
66176
|
+
const client = { name: app?.name || doc?.name || null, redirect, scope: params.get("scope") || "read" };
|
|
66177
|
+
if (external && !proved && !api.store.getConfig()?.uiPassword && !api.redirectAllowed(redirect)) {
|
|
66178
|
+
if (req.method === "GET" && ownerWebId) return sendPodSigninPage(res, client, ownerWebId, api.mount || "");
|
|
66131
66179
|
api.log(`authorize refused: no UI password, and "${redirect}" is not an address of this agent`);
|
|
66132
66180
|
return send(403, {
|
|
66133
66181
|
error: "this client asks to be sent somewhere other than this agent, and no password is set to approve that with. Run `fedipod passwd` and try again."
|
|
66134
66182
|
});
|
|
66135
66183
|
}
|
|
66136
|
-
const client = { name: app?.name || doc?.name || null, redirect, scope: params.get("scope") || "read" };
|
|
66137
66184
|
if (app) {
|
|
66138
66185
|
if (!app.redirectUris.includes(redirect)) {
|
|
66139
66186
|
api.log(`authorize refused: redirect_uri "${redirect}" not registered for ${app.clientId}`);
|
|
@@ -66152,7 +66199,7 @@ async function handle(api, ctx) {
|
|
|
66152
66199
|
api.log(`authorize refused: redirect_uri "${redirect}" is not this agent`);
|
|
66153
66200
|
return send(400, { error: "redirect_uri must be an address of this agent" });
|
|
66154
66201
|
}
|
|
66155
|
-
if (req.method === "POST") {
|
|
66202
|
+
if (req.method === "POST" && !proved) {
|
|
66156
66203
|
if (api.rateLimited()) {
|
|
66157
66204
|
api.log("authorize rate limited");
|
|
66158
66205
|
return sendLoginForm(
|
|
@@ -66167,12 +66214,13 @@ async function handle(api, ctx) {
|
|
|
66167
66214
|
if (!pw || !checkPassword(pw, body.password || "")) {
|
|
66168
66215
|
return sendLoginForm(res, params, "wrong password \u2014 try again", client);
|
|
66169
66216
|
}
|
|
66170
|
-
} else if (pw) {
|
|
66217
|
+
} else if (pw && !proved) {
|
|
66171
66218
|
return sendLoginForm(res, params, "", client);
|
|
66172
|
-
} else if (api.allowed && !api.allowed.isLocalRequest(req)) {
|
|
66219
|
+
} else if (!proved && api.allowed && !api.allowed.isLocalRequest(req) && !api.throughDoor?.(req)) {
|
|
66220
|
+
if (req.method === "GET" && ownerWebId) return sendPodSigninPage(res, client, ownerWebId, api.mount || "");
|
|
66173
66221
|
api.log(`authorize refused: no UI password, and "${req.headers.host}" is not this machine`);
|
|
66174
66222
|
return send(403, {
|
|
66175
|
-
error: "
|
|
66223
|
+
error: api.embedded ? "Open your account's management page first, then sign in from there." : "Signing in from another machine needs a password. On the machine that runs your account, run: fedipod passwd"
|
|
66176
66224
|
});
|
|
66177
66225
|
}
|
|
66178
66226
|
const code = external ? api.mintCode({
|
|
@@ -66186,6 +66234,7 @@ async function handle(api, ctx) {
|
|
|
66186
66234
|
const target = new URL(redirect);
|
|
66187
66235
|
target.searchParams.set("code", code);
|
|
66188
66236
|
if (params.get("state")) target.searchParams.set("state", params.get("state"));
|
|
66237
|
+
if (proved) return send(200, { redirect: target.href });
|
|
66189
66238
|
res.writeHead(302, { location: target.href });
|
|
66190
66239
|
res.end();
|
|
66191
66240
|
return true;
|
|
@@ -68091,6 +68140,7 @@ var MastoApi = class _MastoApi {
|
|
|
68091
68140
|
allowed = null,
|
|
68092
68141
|
scheme = null,
|
|
68093
68142
|
embedded = false,
|
|
68143
|
+
throughDoor = null,
|
|
68094
68144
|
mount = "",
|
|
68095
68145
|
streaming = true,
|
|
68096
68146
|
webPush = true,
|
|
@@ -68101,6 +68151,7 @@ var MastoApi = class _MastoApi {
|
|
|
68101
68151
|
this.embedded = embedded;
|
|
68102
68152
|
this.log = log2;
|
|
68103
68153
|
this.allowed = allowed;
|
|
68154
|
+
this.throughDoor = throughDoor;
|
|
68104
68155
|
this.scheme = scheme;
|
|
68105
68156
|
this.streaming = streaming;
|
|
68106
68157
|
this.webPush = webPush;
|