@rmdes/indiekit-endpoint-activitypub 3.11.2 → 3.11.4

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.
@@ -67,18 +67,10 @@ export function createMastodonRouter({ collections, pluginOptions = {} }) {
67
67
 
68
68
  // ─── Body parsers ───────────────────────────────────────────────────────
69
69
  // Mastodon clients send JSON, form-urlencoded, and occasionally text/plain.
70
- // Skip multipart/form-data requests multer handles those in media routes.
71
- // If express.json/urlencoded consume the stream first, multer gets nothing.
72
- const jsonParser = express.json();
73
- const urlencodedParser = express.urlencoded({ extended: true });
74
- router.use("/api", (req, res, next) => {
75
- if (req.is("multipart/form-data")) return next();
76
- jsonParser(req, res, next);
77
- });
78
- router.use("/api", (req, res, next) => {
79
- if (req.is("multipart/form-data")) return next();
80
- urlencodedParser(req, res, next);
81
- });
70
+ // Note: multipart/form-data is handled globally by express-fileupload
71
+ // (configured in Indiekit's express.js), so no multer needed here.
72
+ router.use("/api", express.json());
73
+ router.use("/api", express.urlencoded({ extended: true }));
82
74
  router.use("/oauth", express.json());
83
75
  router.use("/oauth", express.urlencoded({ extended: true }));
84
76
 
@@ -5,18 +5,16 @@
5
5
  * POST /api/v1/media — legacy upload (same as v2)
6
6
  * GET /api/v1/media/:id — get media attachment metadata
7
7
  * PUT /api/v1/media/:id — update media metadata (description/focus)
8
+ *
9
+ * File uploads are handled by express-fileupload (configured globally by
10
+ * Indiekit's express.js). Files arrive on req.files, NOT req.file (multer).
8
11
  */
9
12
  import express from "express";
10
- import multer from "multer";
11
13
  import { ObjectId } from "mongodb";
12
14
  import { tokenRequired } from "../middleware/token-required.js";
13
15
  import { scopeRequired } from "../middleware/scope-required.js";
14
16
 
15
17
  const router = express.Router(); // eslint-disable-line new-cap
16
- const upload = multer({
17
- storage: multer.memoryStorage(),
18
- limits: { fileSize: 40 * 1024 * 1024 },
19
- });
20
18
 
21
19
  /**
22
20
  * Determine Mastodon media type from MIME type.
@@ -54,6 +52,7 @@ function serializeMediaAttachment(doc) {
54
52
 
55
53
  /**
56
54
  * Upload file to the Micropub media endpoint.
55
+ * Accepts an express-fileupload file object (has .data Buffer, .mimetype, .name).
57
56
  * Returns the URL from the Location header.
58
57
  */
59
58
  async function uploadToMediaEndpoint(file, application, token) {
@@ -67,8 +66,8 @@ async function uploadToMediaEndpoint(file, application, token) {
67
66
  : new URL(mediaEndpoint, application.url).href;
68
67
 
69
68
  const formData = new FormData();
70
- const blob = new Blob([file.buffer], { type: file.mimetype });
71
- formData.append("file", blob, file.originalname);
69
+ const blob = new Blob([file.data], { type: file.mimetype });
70
+ formData.append("file", blob, file.name);
72
71
 
73
72
  const response = await fetch(mediaUrl, {
74
73
  method: "POST",
@@ -95,15 +94,20 @@ router.post(
95
94
  "/api/v2/media",
96
95
  tokenRequired,
97
96
  scopeRequired("write", "write:media"),
98
- upload.single("file"),
99
97
  async (req, res, next) => {
100
98
  try {
101
99
  const { application } = req.app.locals;
102
100
  const collections = req.app.locals.mastodonCollections;
101
+ // Use IndieAuth token stored during OAuth authorization, falling back
102
+ // to session token (native reader) or Mastodon token (won't work for
103
+ // Micropub media endpoint but covers direct internal calls).
103
104
  const token =
104
- req.session?.access_token || req.mastodonToken?.accessToken;
105
+ req.session?.access_token ||
106
+ req.mastodonToken?.indieauthToken ||
107
+ req.mastodonToken?.accessToken;
105
108
 
106
- if (!req.file) {
109
+ const file = req.files?.file;
110
+ if (!file) {
107
111
  return res.status(422).json({ error: "No file provided" });
108
112
  }
109
113
 
@@ -113,17 +117,13 @@ router.post(
113
117
  .json({ error: "Authentication required for media upload" });
114
118
  }
115
119
 
116
- const fileUrl = await uploadToMediaEndpoint(
117
- req.file,
118
- application,
119
- token,
120
- );
120
+ const fileUrl = await uploadToMediaEndpoint(file, application, token);
121
121
 
122
122
  const doc = {
123
123
  url: fileUrl,
124
124
  description: req.body.description || "",
125
125
  focus: req.body.focus || null,
126
- mimeType: req.file.mimetype,
126
+ mimeType: file.mimetype,
127
127
  createdAt: new Date(),
128
128
  };
129
129
 
@@ -143,15 +143,20 @@ router.post(
143
143
  "/api/v1/media",
144
144
  tokenRequired,
145
145
  scopeRequired("write", "write:media"),
146
- upload.single("file"),
147
146
  async (req, res, next) => {
148
147
  try {
149
148
  const { application } = req.app.locals;
150
149
  const collections = req.app.locals.mastodonCollections;
150
+ // Use IndieAuth token stored during OAuth authorization, falling back
151
+ // to session token (native reader) or Mastodon token (won't work for
152
+ // Micropub media endpoint but covers direct internal calls).
151
153
  const token =
152
- req.session?.access_token || req.mastodonToken?.accessToken;
154
+ req.session?.access_token ||
155
+ req.mastodonToken?.indieauthToken ||
156
+ req.mastodonToken?.accessToken;
153
157
 
154
- if (!req.file) {
158
+ const file = req.files?.file;
159
+ if (!file) {
155
160
  return res.status(422).json({ error: "No file provided" });
156
161
  }
157
162
 
@@ -161,17 +166,13 @@ router.post(
161
166
  .json({ error: "Authentication required for media upload" });
162
167
  }
163
168
 
164
- const fileUrl = await uploadToMediaEndpoint(
165
- req.file,
166
- application,
167
- token,
168
- );
169
+ const fileUrl = await uploadToMediaEndpoint(file, application, token);
169
170
 
170
171
  const doc = {
171
172
  url: fileUrl,
172
173
  description: req.body.description || "",
173
174
  focus: req.body.focus || null,
174
- mimeType: req.file.mimetype,
175
+ mimeType: file.mimetype,
175
176
  createdAt: new Date(),
176
177
  };
177
178
 
@@ -388,6 +388,9 @@ router.post("/oauth/authorize", async (req, res, next) => {
388
388
  redirectUri: redirect_uri,
389
389
  codeChallenge: code_challenge || null,
390
390
  codeChallengeMethod: code_challenge_method || null,
391
+ // Store the IndieAuth session token so Mastodon API routes can call
392
+ // Micropub/media endpoints on behalf of this user (single-user server).
393
+ indieauthToken: req.session?.access_token || null,
391
394
  createdAt: new Date(),
392
395
  expiresAt: new Date(Date.now() + 10 * 60 * 1000), // 10 minutes
393
396
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rmdes/indiekit-endpoint-activitypub",
3
- "version": "3.11.2",
3
+ "version": "3.11.4",
4
4
  "description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.",
5
5
  "keywords": [
6
6
  "indiekit",
@@ -45,7 +45,6 @@
45
45
  "express": "^5.0.0",
46
46
  "express-rate-limit": "^7.5.1",
47
47
  "ioredis": "^5.9.3",
48
- "multer": "^2.1.1",
49
48
  "sanitize-html": "^2.13.1",
50
49
  "unfurl.js": "^6.4.0"
51
50
  },