Skip to content

Commit e380f74

Browse files
Fix Mastodon API endpoints for real client compatibility
- Skip WAC auth for /api/v1/* routes (clients need public access) - Parse form-urlencoded bodies (Mastodon clients use form encoding) - Use stable created_at timestamp instead of per-request new Date() - Derive ws/wss protocol from request instead of hardcoding wss - HTML-escape summary in verify_credentials response
1 parent 785473d commit e380f74

2 files changed

Lines changed: 31 additions & 6 deletions

File tree

src/ap/routes/mastodon.js

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,34 @@
1010
// In-memory client store (replace with persistent storage later)
1111
const clients = new Map()
1212

13+
// Stable instance start time (used for created_at)
14+
const startedAt = new Date().toISOString()
15+
16+
/**
17+
* Parse request body — handles both JSON and form-urlencoded
18+
* (JSS uses raw buffer parser for all content types)
19+
*/
20+
function parseBody (request) {
21+
if (request.body && typeof request.body === 'object' && !Buffer.isBuffer(request.body)) {
22+
return request.body
23+
}
24+
const raw = Buffer.isBuffer(request.body) ? request.body.toString() : String(request.body || '')
25+
const ct = request.headers['content-type'] || ''
26+
if (ct.includes('application/json')) {
27+
try { return JSON.parse(raw) } catch { return {} }
28+
}
29+
// Default: parse as form-urlencoded
30+
return Object.fromEntries(new URLSearchParams(raw))
31+
}
32+
1333
/**
1434
* POST /api/v1/apps — Dynamic client registration
1535
* Mastodon clients call this to register before OAuth
1636
*/
1737
export function createAppsHandler () {
1838
return async (request, reply) => {
19-
const { client_name, redirect_uris, scopes, website } = request.body || {}
39+
const body = parseBody(request)
40+
const { client_name, redirect_uris, scopes, website } = body
2041

2142
if (!client_name || !redirect_uris) {
2243
return reply.code(422).send({ error: 'client_name and redirect_uris are required' })
@@ -56,14 +77,14 @@ export function createVerifyCredentialsHandler (config) {
5677
username: config.username,
5778
acct: config.username,
5879
display_name: config.displayName,
59-
note: config.summary ? `<p>${config.summary}</p>` : '',
80+
note: config.summary ? `<p>${escapeHtml(config.summary)}</p>` : '',
6081
url: `${baseUrl}/profile/card`,
6182
uri: `${baseUrl}/profile/card#me`,
6283
avatar: `${baseUrl}/profile/avatar.png`,
6384
header: '',
6485
locked: false,
6586
bot: false,
66-
created_at: new Date().toISOString(),
87+
created_at: startedAt,
6788
followers_count: 0,
6889
following_count: 0,
6990
statuses_count: 0,
@@ -87,7 +108,7 @@ export function createInstanceHandler (config) {
87108
return async (request, reply) => {
88109
const protocol = request.headers['x-forwarded-proto'] || request.protocol
89110
const host = request.headers['x-forwarded-host'] || request.hostname
90-
const baseUrl = `${protocol}://${host}`
111+
const wsProtocol = protocol === 'https' ? 'wss' : 'ws'
91112

92113
return reply.send({
93114
uri: host,
@@ -96,7 +117,7 @@ export function createInstanceHandler (config) {
96117
short_description: 'Solid pod with Mastodon-compatible API',
97118
version: '4.0.0 (compatible; JSS 0.0.67)',
98119
urls: {
99-
streaming_api: `wss://${host}`
120+
streaming_api: `${wsProtocol}://${host}`
100121
},
101122
stats: {
102123
user_count: 1,
@@ -121,6 +142,10 @@ export function getClient (clientId) {
121142
return clients.get(clientId) || null
122143
}
123144

145+
function escapeHtml (str) {
146+
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;')
147+
}
148+
124149
export default {
125150
createAppsHandler,
126151
createVerifyCredentialsHandler,

src/server.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ export function createServer(options = {}) {
366366
request.url.startsWith('/solidos-ui/') ||
367367
(nostrEnabled && request.url.startsWith(nostrPath)) ||
368368
(gitEnabled && isGitRequest(request.url)) ||
369-
(activitypubEnabled && apPaths.some(p => request.url === p || request.url.startsWith(p + '?'))) ||
369+
(activitypubEnabled && (apPaths.some(p => request.url === p || request.url.startsWith(p + '?')) || request.url.startsWith('/api/v1/'))) ||
370370
isProfileAP ||
371371
(mongoEnabled && (request.url === '/db' || request.url.startsWith('/db/'))) ||
372372
mashlibPaths.some(p => request.url === p || request.url.startsWith(p + '.'))) {

0 commit comments

Comments
 (0)