- JavaScript 87.7%
- Nunjucks 7.9%
- CSS 4.4%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
The sign-in form decided where this server sent requests. A visitor submits a URL, discoverEndpoints and fetchProfile fetch it, fetchMetadata fetches what its rels point at, and exchangeCode POSTs an authorization code to the authorization endpoint that same page declared. None of those restricted scheme or host, so all four were blind SSRF against loopback, private ranges and 169.254.169.254 — reachable by anyone who can submit the form. Demonstrated with a live loopback server: before this change it was contacted and its declared endpoint adopted. This predates the IndieAuth work; discoverEndpoints fetched unrestricted URLs in 1.0.17 too. It is the same class of issue as harden-client-discovery.patch, sent upstream for endpoint-auth on 2026-08-16, and isFetchableUrl is ported from it rather than re-derived so the two behave identically. A profile URL's host must be a domain name, so every IP literal is refused rather than sorting private ranges from public, and the numeric encodings the resolver accepts but net.isIP does not (2130706433, 0x7f000001, 0177.0.0.1) are refused explicitly. The patch guards one URL. That is not enough here: fetches followed redirects, so a permitted domain answering 302 to 169.254.169.254 walked straight through. safeFetch follows redirects by hand and re-checks every hop, caps the chain, applies a 5s timeout, and never follows a redirect for a non-GET request — replaying the POST body would hand the authorization code to whoever set the Location header. A domain name is still trusted without resolving it, so one pointing at an internal address passes. Closing that needs a resolving dispatcher. Verified unchanged against live sites: rmendes.net and aaronparecki.com resolve to their own endpoints, tantek.com to indieauth.com, profiles still parse. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
| assets | ||
| lib | ||
| locales | ||
| tests | ||
| views | ||
| .gitignore | ||
| index.js | ||
| package.json | ||
| README.md | ||
@rmdes/indiekit-endpoint-comments
Comment endpoint for Indiekit. Allows visitors to authenticate via IndieAuth and post comments on blog posts. Site owners can reply to comments from the admin session.
Features
- IndieAuth authentication — visitors sign in with their personal URL via IndieAuth/PKCE
- Comment submission — authenticated visitors can post comments on any page
- Owner replies — site owner can reply to native comments from the admin session
- Rate limiting — per-user rate limits (configurable per hour/per day)
- Content sanitization — HTML sanitization with configurable max length
- Admin moderation — hide (soft delete), restore, and purge comments
- JF2 API — comments served in JF2 feed format for frontend consumption
Installation
npm install @rmdes/indiekit-endpoint-comments
// indiekit.config.js
import CommentsEndpoint from "@rmdes/indiekit-endpoint-comments";
export default {
plugins: [
new CommentsEndpoint({
mountPath: "/comments",
maxLength: 2000,
rateLimit: { perHour: 5, perDay: 20 },
}),
],
};
API
Public Routes (no authentication required)
| Method | Path | Description |
|---|---|---|
| GET | /comments/api/comments?target={url} |
Get comments for a target URL (JF2 feed) |
| GET | /comments/api/session |
Check current IndieAuth session |
| GET | /comments/api/is-owner |
Check if visitor is the site owner (admin session) |
| POST | /comments/api/submit |
Submit a comment (requires IndieAuth session) |
| POST | /comments/api/reply |
Submit an owner reply to a comment (requires admin session) |
| POST | /comments/api/auth |
Start IndieAuth flow |
| GET | /comments/auth/callback |
IndieAuth callback handler |
Admin Routes (require Indiekit authentication)
| Method | Path | Description |
|---|---|---|
| GET | /comments |
Admin dashboard with stats and moderation |
| POST | /comments/hide |
Soft-delete a comment |
| POST | /comments/purge |
Permanently delete a comment |
Owner Reply (POST /comments/api/reply)
Allows the site owner to reply to native comments. Requires an active Indiekit admin session (not an IndieAuth comment session).
Request:
{
"parent_id": "comment-id-123",
"content": "Thanks for the comment!",
"target": "https://example.com/posts/hello/"
}
Response:
{
"success": true,
"comment": {
"type": "entry",
"author": {
"name": "Site Owner",
"url": "https://example.com",
"photo": "https://..."
},
"content": {
"text": "Thanks for the comment!",
"html": "<p>Thanks for the comment!</p>"
},
"published": "2026-03-15T12:00:00.000Z"
}
}
Owner Detection (GET /comments/api/is-owner)
Returns owner status and available syndication targets for the reply-to-interactions feature. Used by the frontend to show reply buttons on interactions.
Response (when owner):
{
"isOwner": true,
"name": "Ricardo Mendes",
"url": "https://rmendes.net",
"photo": "https://...",
"syndicationTargets": {
"bluesky": "https://bsky.social",
"mastodon": "https://indieweb.social"
}
}
The syndicationTargets map is auto-detected from Indiekit's registered syndicators. The frontend uses these to route replies to the correct platform via Micropub.
Architecture
Comment Types
This plugin handles two distinct reply flows:
| Flow | Trigger | Route | Session |
|---|---|---|---|
| Visitor comment | Visitor submits via comment form | POST /api/submit |
IndieAuth comment session |
| Owner reply to comment | Owner replies to a native comment | POST /api/reply |
Indiekit admin session |
Reply-to-Interactions (Micropub flow)
When the site owner replies to platform interactions (Mastodon replies, Bluesky mentions, IndieWeb webmentions), the reply goes through Micropub — not through this plugin. The frontend posts to /micropub with in-reply-to set to the interaction's URL and optional mp-syndicate-to for platform-specific threading.
This separation exists because:
- Native comments are stored in the
commentscollection and managed by this plugin - Platform interactions are stored in the
conversation_itemscollection by@rmdes/indiekit-endpoint-conversations - Owner replies to interactions are Micropub posts stored in the
postscollection
Collections
| Collection | Purpose |
|---|---|
comments |
Native visitor comments |
comment_sessions |
IndieAuth session state (PKCE, tokens) |
Dependencies
indiekit-eleventy-theme— The theme'scomments.jsprovides the Alpine.js comment form, IndieAuth flow, and inline reply UI.webmentions.jsprovides reply buttons on platform interactions.@rmdes/indiekit-endpoint-conversations— Handles platform interactions (Mastodon/Bluesky/AP). Owner replies to platform interactions go through Micropub, andconversationsenriches its API with those replies for frontend threading.
License
MIT