diff --git a/index.js b/index.js index dfc2b93..a61e91d 100644 --- a/index.js +++ b/index.js @@ -60,6 +60,7 @@ import { } from "./lib/controllers/featured-tags.js"; import { resolveController } from "./lib/controllers/resolve.js"; import { publicProfileController } from "./lib/controllers/public-profile.js"; +import { authorizeInteractionController } from "./lib/controllers/authorize-interaction.js"; import { myProfileController } from "./lib/controllers/my-profile.js"; import { noteObjectController } from "./lib/controllers/note-object.js"; import { @@ -173,6 +174,10 @@ export default class ActivityPubEndpoint { // dereference the Note ID during Create activity verification. router.get("/quick-replies/:id", noteObjectController(self)); + // Authorize interaction — remote follow / subscribe endpoint. + // Remote servers redirect users here via the WebFinger subscribe template. + router.get("/authorize_interaction", authorizeInteractionController(self)); + // HTML fallback for actor URL — serve a public profile page. // Fedify only serves JSON-LD; browsers get 406 and fall through here. router.get("/users/:identifier", publicProfileController(self)); diff --git a/lib/controllers/authorize-interaction.js b/lib/controllers/authorize-interaction.js new file mode 100644 index 0000000..2d71d8a --- /dev/null +++ b/lib/controllers/authorize-interaction.js @@ -0,0 +1,45 @@ +/** + * Authorize Interaction controller — handles the remote follow / authorize + * interaction flow for ActivityPub federation. + * + * When a remote server (WordPress AP, Misskey, etc.) discovers our WebFinger + * subscribe template, it redirects the user here with ?uri={actorOrPostUrl}. + * + * Flow: + * 1. Missing uri → render error page + * 2. Unauthenticated → redirect to login, then back here + * 3. Authenticated → redirect to the reader's remote profile page + */ + +export function authorizeInteractionController(plugin) { + return async (req, res) => { + const uri = req.query.uri || req.query.acct; + if (!uri) { + return res.status(400).render("activitypub-authorize-interaction", { + title: "Authorize Interaction", + mountPath: plugin.options.mountPath, + error: "Missing uri parameter", + }); + } + + // Clean up acct: prefix if present + const resource = uri.replace(/^acct:/, ""); + + // Check authentication — if not logged in, redirect to login + // then back to this page after auth + const session = req.session; + if (!session?.access_token) { + const returnUrl = `${plugin.options.mountPath}/authorize_interaction?uri=${encodeURIComponent(uri)}`; + return res.redirect( + `/session/login?redirect=${encodeURIComponent(returnUrl)}`, + ); + } + + // Authenticated — redirect to the remote profile viewer in our reader + // which already has follow/unfollow/like/boost functionality + const encodedUrl = encodeURIComponent(resource); + return res.redirect( + `${plugin.options.mountPath}/admin/reader/profile?url=${encodedUrl}`, + ); + }; +} diff --git a/lib/federation-setup.js b/lib/federation-setup.js index 8f7917e..bc7f2e3 100644 --- a/lib/federation-setup.js +++ b/lib/federation-setup.js @@ -262,6 +262,18 @@ export function setupFederation(options) { // instance actor's keys for outgoing fetches), which Fedify doesn't yet // support out of the box. Re-enable once Fedify adds this capability. + // --- WebFinger custom links --- + // Add OStatus subscribe template so remote servers (WordPress AP, Misskey, etc.) + // can redirect users to our authorize_interaction page for remote follow. + federation.setWebFingerLinksDispatcher((_ctx, _resource) => { + return [ + { + rel: "http://ostatus.org/schema/1.0/subscribe", + template: `${publicationUrl}${mountPath.replace(/^\//, "")}/authorize_interaction?uri={uri}`, + }, + ]; + }); + // --- Inbox listeners --- const inboxChain = federation.setInboxListeners( `${mountPath}/users/{identifier}/inbox`, diff --git a/package.json b/package.json index 9ec1014..44899aa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@rmdes/indiekit-endpoint-activitypub", - "version": "2.0.24", + "version": "2.0.25", "description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.", "keywords": [ "indiekit", diff --git a/views/activitypub-authorize-interaction.njk b/views/activitypub-authorize-interaction.njk new file mode 100644 index 0000000..c10941e --- /dev/null +++ b/views/activitypub-authorize-interaction.njk @@ -0,0 +1,10 @@ +{% extends "document.njk" %} + +{% from "prose/macro.njk" import prose with context %} + +{% block content %} + {% if error %} + {{ prose({ text: error }) }} + {% endif %} +

Return to dashboard

+{% endblock %}