fedipod-server 0.19.0 → 0.20.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/claims.js CHANGED
@@ -19,7 +19,8 @@ function claims(input, frontHost) {
19
19
  const bare = String(input.host).split(':')[0].toLowerCase();
20
20
  if (bare !== String(frontHost).toLowerCase())
21
21
  return false; // a pod subdomain → not ours
22
- return FRONT_PATHS.has(input.pathname) || input.pathname.startsWith('/u/');
22
+ return FRONT_PATHS.has(input.pathname) || input.pathname.startsWith('/u/')
23
+ || input.pathname.startsWith('/@'); // the short profile address
23
24
  }
24
25
  // What an identity answers on its own pod's origin: the protocol routes other
25
26
  // software addresses it by, and the one path its owner's pages live under.
@@ -35,7 +36,7 @@ const AGENT_PATHS = new Set([
35
36
  // at this name before, so no pod resource is displaced.
36
37
  '/.well-known/oauth-authorization-server',
37
38
  ]);
38
- const AGENT_PREFIXES = ['/api/', '/oauth/'];
39
+ const AGENT_PREFIXES = ['/api/', '/oauth/', '/@']; // /@handle: the short profile address
39
40
  // An identity's inbox is the container `/<root>/ap/inbox/`, matched by shape
40
41
  // rather than a fixed root. A POST here is a delivery, verified at the door
41
42
  // before it is written; the handler checks the exact path per identity. The
@@ -143,7 +143,7 @@ export class Publisher {
143
143
  }
144
144
 
145
145
  await discovery.writeWebfinger(this.remote, urls,
146
- wire.jrd({ handle: this.config.handle, host, actor: urls.actor }));
146
+ wire.jrd({ handle: this.config.handle, host, actor: urls.actor, page: urls.profileHtml }));
147
147
  await discovery.writeHostMeta(this.remote, urls, wire.hostMeta(urls.base));
148
148
 
149
149
  const nodeinfoDocUrl = urls.home + 'ap/nodeinfo-2.0';
package/lib/core/wire.mjs CHANGED
@@ -52,11 +52,14 @@ export function hostMeta(base) {
52
52
  return `<?xml version="1.0" encoding="UTF-8"?>\n<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">\n <Link rel="lrdd" template="${base}.well-known/webfinger?resource={uri}"/>\n</XRD>\n`;
53
53
  }
54
54
 
55
- export function jrd({ handle, host, actor, aliases = [] }) {
55
+ export function jrd({ handle, host, actor, aliases = [], page = null }) {
56
56
  return {
57
57
  subject: `acct:${handle}@${host}`,
58
58
  ...(aliases.length ? { aliases } : {}),
59
- links: [{ rel: 'self', type: 'application/activity+json', href: actor }],
59
+ links: [
60
+ { rel: 'self', type: 'application/activity+json', href: actor },
61
+ ...(page ? [{ rel: 'http://webfinger.net/rel/profile-page', type: 'text/html', href: page }] : []),
62
+ ],
60
63
  };
61
64
  }
62
65
 
@@ -181,6 +181,14 @@ export function buildAdminSurface({ agent, gate, allowed, log = console.log,
181
181
  const scheme = req.socket.encrypted || req.headers['x-forwarded-proto'] === 'https' ? 'https' : 'http';
182
182
  return json(res, 200, masto.authorizationServerMetadata(`${scheme}://${req.headers.host}${mount}`));
183
183
  }
184
+ // The short address a person types: /@handle opens the profile page.
185
+ const at = /^\/@([^/]+)$/u.exec(p);
186
+ const ownHandle = handle || agent.store?.getConfig?.()?.handle || '';
187
+ if (at && ownHandle && agent.publisher?.urls?.profileHtml
188
+ && decodeURIComponent(at[1]).toLowerCase() === String(ownHandle).toLowerCase()) {
189
+ res.writeHead(302, { location: agent.publisher.urls.profileHtml, 'cache-control': 'no-store' });
190
+ res.end(); return;
191
+ }
184
192
  if (atDoor && gate(req, res)) return;
185
193
  if (p === '/api/v1/streaming/health') {
186
194
  res.writeHead(200, { 'content-type': 'text/plain' }); res.end('OK'); return;
@@ -26,11 +26,17 @@ import { podBaseOfWebId } from '../pod/urls.mjs';
26
26
  // wire.mjs: wire drags the agent's whole HTML pipeline (sanitize-html and
27
27
  // friends), which a serverless front must never carry — it crashed the
28
28
  // deployed function before it answered its first request.
29
- const jrd = ({ handle, host, actor, aliases = [] }) => ({
29
+ const jrd = ({ handle, host, actor, aliases = [], page = null }) => ({
30
30
  subject: `acct:${handle}@${host}`,
31
31
  ...(aliases.length ? { aliases } : {}),
32
- links: [{ rel: 'self', type: 'application/activity+json', href: actor }],
32
+ links: [
33
+ { rel: 'self', type: 'application/activity+json', href: actor },
34
+ ...(page ? [{ rel: 'http://webfinger.net/rel/profile-page', type: 'text/html', href: page }] : []),
35
+ ],
33
36
  });
37
+ // The public profile page of any account the front answers for lives on
38
+ // its pod, whether the address is the front's or the pod's own.
39
+ const profilePageOf = (rec) => (rec?.podHome ? rec.podHome + 'ap/profile.html' : null);
34
40
 
35
41
  const AP_CT = 'application/activity+json';
36
42
 
@@ -600,13 +606,29 @@ async function route(request, ctx) {
600
606
  // is the alias, so a client signing in by the fronted address can find
601
607
  // the pod (and its login) without a lookup only the host could answer.
602
608
  const podActor = rec.inboxOnly ? [] : [rec.podHome + 'ap/actor'];
603
- const out = j(200, jrd({ handle: m[1], host: ctx.host, actor: rec.actorUrl, aliases: podActor }),
609
+ const out = j(200, jrd({ handle: m[1], host: ctx.host, actor: rec.actorUrl, aliases: podActor, page: profilePageOf(rec) }),
604
610
  'application/jrd+json');
605
611
  // RFC 7033: a JRD is read from any origin.
606
612
  out.headers['access-control-allow-origin'] = '*';
607
613
  return out;
608
614
  }
609
615
 
616
+ // The short address a person types: https://<host>/@handle opens the
617
+ // profile page, the way most Fediverse servers spell it. /@handle@pod-host
618
+ // is the same for an account whose address is its pod's, which the front
619
+ // holds under that full address — the form other servers use for a
620
+ // profile that lives elsewhere.
621
+ const at = /^\/@([^/@]+)(?:@([^/@]+))?$/u.exec(pathname);
622
+ if (at) {
623
+ const handle = decodeURIComponent(at[1]).toLowerCase();
624
+ const host = at[2] ? decodeURIComponent(at[2]).toLowerCase() : null;
625
+ const key = host && host !== String(ctx.host).toLowerCase() ? `${handle}@${host}` : handle;
626
+ const rec = await ctx.lookup(key);
627
+ const page = rec && profilePageOf(rec);
628
+ if (!page) return notFound();
629
+ return { status: 302, headers: { location: page, 'cache-control': 'no-store' }, body: '' };
630
+ }
631
+
610
632
  const up = parseUserPath(pathname);
611
633
  if (!up) return notFound();
612
634
  const rec = await ctx.lookup(up.handle);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fedipod-server",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
4
4
  "description": "The FediPod Server: a full ActivityPub server as a Community Solid Server component.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -9664,11 +9664,14 @@ function hostMeta(base) {
9664
9664
  </XRD>
9665
9665
  `;
9666
9666
  }
9667
- function jrd({ handle: handle7, host, actor, aliases = [] }) {
9667
+ function jrd({ handle: handle7, host, actor, aliases = [], page: page2 = null }) {
9668
9668
  return {
9669
9669
  subject: `acct:${handle7}@${host}`,
9670
9670
  ...aliases.length ? { aliases } : {},
9671
- links: [{ rel: "self", type: "application/activity+json", href: actor }]
9671
+ links: [
9672
+ { rel: "self", type: "application/activity+json", href: actor },
9673
+ ...page2 ? [{ rel: "http://webfinger.net/rel/profile-page", type: "text/html", href: page2 }] : []
9674
+ ]
9672
9675
  };
9673
9676
  }
9674
9677
  function followsNeedApproval(config = {}) {
@@ -56784,7 +56787,7 @@ var Publisher = class {
56784
56787
  await writeWebfinger(
56785
56788
  this.remote,
56786
56789
  urls,
56787
- jrd({ handle: this.config.handle, host, actor: urls.actor })
56790
+ jrd({ handle: this.config.handle, host, actor: urls.actor, page: urls.profileHtml })
56788
56791
  );
56789
56792
  await writeHostMeta(this.remote, urls, hostMeta(urls.base));
56790
56793
  const nodeinfoDocUrl = urls.home + "ap/nodeinfo-2.0";