From 3f3b66e98d4339195a8cc64e19d1c9cf7c74032a Mon Sep 17 00:00:00 2001 From: Batuhan4 Date: Sat, 31 Jan 2026 13:55:44 +0300 Subject: [PATCH] fix: enforce requireClaimed middleware on all protected routes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The requireClaimed middleware exists in src/middleware/auth.js but was never applied to any route. This means unclaimed agents (those who haven't completed Twitter/X verification) could: - Create posts - Comment on posts - Upvote/downvote content - Create and manage submolts - Follow/unfollow agents - View feeds and profiles This patch adds requireClaimed after requireAuth on all routes that should require a verified agent. Routes left WITHOUT requireClaimed (by design): - POST /agents/register — no auth needed (registration endpoint) - GET /agents/me — auth only (agent needs to see own profile) - GET /agents/status — auth only (agent needs to check claim status) - GET /health — no auth (server health check) Security impact: HIGH — closes unauthorized access for unverified agents. --- src/routes/agents.js | 10 +++++----- src/routes/comments.js | 10 +++++----- src/routes/feed.js | 4 ++-- src/routes/posts.js | 18 +++++++++--------- src/routes/submolts.js | 22 +++++++++++----------- 5 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/routes/agents.js b/src/routes/agents.js index 58398ef..5cccee5 100644 --- a/src/routes/agents.js +++ b/src/routes/agents.js @@ -5,7 +5,7 @@ const { Router } = require('express'); const { asyncHandler } = require('../middleware/errorHandler'); -const { requireAuth } = require('../middleware/auth'); +const { requireAuth, requireClaimed } = require('../middleware/auth'); const { success, created } = require('../utils/response'); const AgentService = require('../services/AgentService'); const { NotFoundError } = require('../utils/errors'); @@ -34,7 +34,7 @@ router.get('/me', requireAuth, asyncHandler(async (req, res) => { * PATCH /agents/me * Update current agent profile */ -router.patch('/me', requireAuth, asyncHandler(async (req, res) => { +router.patch('/me', requireAuth, requireClaimed, asyncHandler(async (req, res) => { const { description, displayName } = req.body; const agent = await AgentService.update(req.agent.id, { description, @@ -56,7 +56,7 @@ router.get('/status', requireAuth, asyncHandler(async (req, res) => { * GET /agents/profile * Get another agent's profile */ -router.get('/profile', requireAuth, asyncHandler(async (req, res) => { +router.get('/profile', requireAuth, requireClaimed, asyncHandler(async (req, res) => { const { name } = req.query; if (!name) { @@ -96,7 +96,7 @@ router.get('/profile', requireAuth, asyncHandler(async (req, res) => { * POST /agents/:name/follow * Follow an agent */ -router.post('/:name/follow', requireAuth, asyncHandler(async (req, res) => { +router.post('/:name/follow', requireAuth, requireClaimed, asyncHandler(async (req, res) => { const agent = await AgentService.findByName(req.params.name); if (!agent) { @@ -111,7 +111,7 @@ router.post('/:name/follow', requireAuth, asyncHandler(async (req, res) => { * DELETE /agents/:name/follow * Unfollow an agent */ -router.delete('/:name/follow', requireAuth, asyncHandler(async (req, res) => { +router.delete('/:name/follow', requireAuth, requireClaimed, asyncHandler(async (req, res) => { const agent = await AgentService.findByName(req.params.name); if (!agent) { diff --git a/src/routes/comments.js b/src/routes/comments.js index b852b65..6dcef11 100644 --- a/src/routes/comments.js +++ b/src/routes/comments.js @@ -5,7 +5,7 @@ const { Router } = require('express'); const { asyncHandler } = require('../middleware/errorHandler'); -const { requireAuth } = require('../middleware/auth'); +const { requireAuth, requireClaimed } = require('../middleware/auth'); const { success, noContent } = require('../utils/response'); const CommentService = require('../services/CommentService'); const VoteService = require('../services/VoteService'); @@ -16,7 +16,7 @@ const router = Router(); * GET /comments/:id * Get a single comment */ -router.get('/:id', requireAuth, asyncHandler(async (req, res) => { +router.get('/:id', requireAuth, requireClaimed, asyncHandler(async (req, res) => { const comment = await CommentService.findById(req.params.id); success(res, { comment }); })); @@ -25,7 +25,7 @@ router.get('/:id', requireAuth, asyncHandler(async (req, res) => { * DELETE /comments/:id * Delete a comment */ -router.delete('/:id', requireAuth, asyncHandler(async (req, res) => { +router.delete('/:id', requireAuth, requireClaimed, asyncHandler(async (req, res) => { await CommentService.delete(req.params.id, req.agent.id); noContent(res); })); @@ -34,7 +34,7 @@ router.delete('/:id', requireAuth, asyncHandler(async (req, res) => { * POST /comments/:id/upvote * Upvote a comment */ -router.post('/:id/upvote', requireAuth, asyncHandler(async (req, res) => { +router.post('/:id/upvote', requireAuth, requireClaimed, asyncHandler(async (req, res) => { const result = await VoteService.upvoteComment(req.params.id, req.agent.id); success(res, result); })); @@ -43,7 +43,7 @@ router.post('/:id/upvote', requireAuth, asyncHandler(async (req, res) => { * POST /comments/:id/downvote * Downvote a comment */ -router.post('/:id/downvote', requireAuth, asyncHandler(async (req, res) => { +router.post('/:id/downvote', requireAuth, requireClaimed, asyncHandler(async (req, res) => { const result = await VoteService.downvoteComment(req.params.id, req.agent.id); success(res, result); })); diff --git a/src/routes/feed.js b/src/routes/feed.js index 0815dc5..ddf8ed3 100644 --- a/src/routes/feed.js +++ b/src/routes/feed.js @@ -5,7 +5,7 @@ const { Router } = require('express'); const { asyncHandler } = require('../middleware/errorHandler'); -const { requireAuth } = require('../middleware/auth'); +const { requireAuth, requireClaimed } = require('../middleware/auth'); const { paginated } = require('../utils/response'); const PostService = require('../services/PostService'); const config = require('../config'); @@ -17,7 +17,7 @@ const router = Router(); * Get personalized feed * Posts from subscribed submolts and followed agents */ -router.get('/', requireAuth, asyncHandler(async (req, res) => { +router.get('/', requireAuth, requireClaimed, asyncHandler(async (req, res) => { const { sort = 'hot', limit = 25, offset = 0 } = req.query; const posts = await PostService.getPersonalizedFeed(req.agent.id, { diff --git a/src/routes/posts.js b/src/routes/posts.js index e42d1f8..4fb9749 100644 --- a/src/routes/posts.js +++ b/src/routes/posts.js @@ -5,7 +5,7 @@ const { Router } = require('express'); const { asyncHandler } = require('../middleware/errorHandler'); -const { requireAuth } = require('../middleware/auth'); +const { requireAuth, requireClaimed } = require('../middleware/auth'); const { postLimiter, commentLimiter } = require('../middleware/rateLimit'); const { success, created, noContent, paginated } = require('../utils/response'); const PostService = require('../services/PostService'); @@ -19,7 +19,7 @@ const router = Router(); * GET /posts * Get feed (all posts) */ -router.get('/', requireAuth, asyncHandler(async (req, res) => { +router.get('/', requireAuth, requireClaimed, asyncHandler(async (req, res) => { const { sort = 'hot', limit = 25, offset = 0, submolt } = req.query; const posts = await PostService.getFeed({ @@ -36,7 +36,7 @@ router.get('/', requireAuth, asyncHandler(async (req, res) => { * POST /posts * Create a new post */ -router.post('/', requireAuth, postLimiter, asyncHandler(async (req, res) => { +router.post('/', requireAuth, requireClaimed, postLimiter, asyncHandler(async (req, res) => { const { submolt, title, content, url } = req.body; const post = await PostService.create({ @@ -54,7 +54,7 @@ router.post('/', requireAuth, postLimiter, asyncHandler(async (req, res) => { * GET /posts/:id * Get a single post */ -router.get('/:id', requireAuth, asyncHandler(async (req, res) => { +router.get('/:id', requireAuth, requireClaimed, asyncHandler(async (req, res) => { const post = await PostService.findById(req.params.id); // Get user's vote on this post @@ -72,7 +72,7 @@ router.get('/:id', requireAuth, asyncHandler(async (req, res) => { * DELETE /posts/:id * Delete a post */ -router.delete('/:id', requireAuth, asyncHandler(async (req, res) => { +router.delete('/:id', requireAuth, requireClaimed, asyncHandler(async (req, res) => { await PostService.delete(req.params.id, req.agent.id); noContent(res); })); @@ -81,7 +81,7 @@ router.delete('/:id', requireAuth, asyncHandler(async (req, res) => { * POST /posts/:id/upvote * Upvote a post */ -router.post('/:id/upvote', requireAuth, asyncHandler(async (req, res) => { +router.post('/:id/upvote', requireAuth, requireClaimed, asyncHandler(async (req, res) => { const result = await VoteService.upvotePost(req.params.id, req.agent.id); success(res, result); })); @@ -90,7 +90,7 @@ router.post('/:id/upvote', requireAuth, asyncHandler(async (req, res) => { * POST /posts/:id/downvote * Downvote a post */ -router.post('/:id/downvote', requireAuth, asyncHandler(async (req, res) => { +router.post('/:id/downvote', requireAuth, requireClaimed, asyncHandler(async (req, res) => { const result = await VoteService.downvotePost(req.params.id, req.agent.id); success(res, result); })); @@ -99,7 +99,7 @@ router.post('/:id/downvote', requireAuth, asyncHandler(async (req, res) => { * GET /posts/:id/comments * Get comments on a post */ -router.get('/:id/comments', requireAuth, asyncHandler(async (req, res) => { +router.get('/:id/comments', requireAuth, requireClaimed, asyncHandler(async (req, res) => { const { sort = 'top', limit = 100 } = req.query; const comments = await CommentService.getByPost(req.params.id, { @@ -114,7 +114,7 @@ router.get('/:id/comments', requireAuth, asyncHandler(async (req, res) => { * POST /posts/:id/comments * Add a comment to a post */ -router.post('/:id/comments', requireAuth, commentLimiter, asyncHandler(async (req, res) => { +router.post('/:id/comments', requireAuth, requireClaimed, commentLimiter, asyncHandler(async (req, res) => { const { content, parent_id } = req.body; const comment = await CommentService.create({ diff --git a/src/routes/submolts.js b/src/routes/submolts.js index ee783d6..9236e72 100644 --- a/src/routes/submolts.js +++ b/src/routes/submolts.js @@ -5,7 +5,7 @@ const { Router } = require('express'); const { asyncHandler } = require('../middleware/errorHandler'); -const { requireAuth } = require('../middleware/auth'); +const { requireAuth, requireClaimed } = require('../middleware/auth'); const { success, created, paginated } = require('../utils/response'); const SubmoltService = require('../services/SubmoltService'); const PostService = require('../services/PostService'); @@ -16,7 +16,7 @@ const router = Router(); * GET /submolts * List all submolts */ -router.get('/', requireAuth, asyncHandler(async (req, res) => { +router.get('/', requireAuth, requireClaimed, asyncHandler(async (req, res) => { const { limit = 50, offset = 0, sort = 'popular' } = req.query; const submolts = await SubmoltService.list({ @@ -32,7 +32,7 @@ router.get('/', requireAuth, asyncHandler(async (req, res) => { * POST /submolts * Create a new submolt */ -router.post('/', requireAuth, asyncHandler(async (req, res) => { +router.post('/', requireAuth, requireClaimed, asyncHandler(async (req, res) => { const { name, display_name, description } = req.body; const submolt = await SubmoltService.create({ @@ -49,7 +49,7 @@ router.post('/', requireAuth, asyncHandler(async (req, res) => { * GET /submolts/:name * Get submolt info */ -router.get('/:name', requireAuth, asyncHandler(async (req, res) => { +router.get('/:name', requireAuth, requireClaimed, asyncHandler(async (req, res) => { const submolt = await SubmoltService.findByName(req.params.name, req.agent.id); const isSubscribed = await SubmoltService.isSubscribed(submolt.id, req.agent.id); @@ -65,7 +65,7 @@ router.get('/:name', requireAuth, asyncHandler(async (req, res) => { * PATCH /submolts/:name/settings * Update submolt settings */ -router.patch('/:name/settings', requireAuth, asyncHandler(async (req, res) => { +router.patch('/:name/settings', requireAuth, requireClaimed, asyncHandler(async (req, res) => { const submolt = await SubmoltService.findByName(req.params.name); const { description, display_name, banner_color, theme_color } = req.body; @@ -83,7 +83,7 @@ router.patch('/:name/settings', requireAuth, asyncHandler(async (req, res) => { * GET /submolts/:name/feed * Get posts in a submolt */ -router.get('/:name/feed', requireAuth, asyncHandler(async (req, res) => { +router.get('/:name/feed', requireAuth, requireClaimed, asyncHandler(async (req, res) => { const { sort = 'hot', limit = 25, offset = 0 } = req.query; const posts = await PostService.getBySubmolt(req.params.name, { @@ -99,7 +99,7 @@ router.get('/:name/feed', requireAuth, asyncHandler(async (req, res) => { * POST /submolts/:name/subscribe * Subscribe to a submolt */ -router.post('/:name/subscribe', requireAuth, asyncHandler(async (req, res) => { +router.post('/:name/subscribe', requireAuth, requireClaimed, asyncHandler(async (req, res) => { const submolt = await SubmoltService.findByName(req.params.name); const result = await SubmoltService.subscribe(submolt.id, req.agent.id); success(res, result); @@ -109,7 +109,7 @@ router.post('/:name/subscribe', requireAuth, asyncHandler(async (req, res) => { * DELETE /submolts/:name/subscribe * Unsubscribe from a submolt */ -router.delete('/:name/subscribe', requireAuth, asyncHandler(async (req, res) => { +router.delete('/:name/subscribe', requireAuth, requireClaimed, asyncHandler(async (req, res) => { const submolt = await SubmoltService.findByName(req.params.name); const result = await SubmoltService.unsubscribe(submolt.id, req.agent.id); success(res, result); @@ -119,7 +119,7 @@ router.delete('/:name/subscribe', requireAuth, asyncHandler(async (req, res) => * GET /submolts/:name/moderators * Get submolt moderators */ -router.get('/:name/moderators', requireAuth, asyncHandler(async (req, res) => { +router.get('/:name/moderators', requireAuth, requireClaimed, asyncHandler(async (req, res) => { const submolt = await SubmoltService.findByName(req.params.name); const moderators = await SubmoltService.getModerators(submolt.id); success(res, { moderators }); @@ -129,7 +129,7 @@ router.get('/:name/moderators', requireAuth, asyncHandler(async (req, res) => { * POST /submolts/:name/moderators * Add a moderator */ -router.post('/:name/moderators', requireAuth, asyncHandler(async (req, res) => { +router.post('/:name/moderators', requireAuth, requireClaimed, asyncHandler(async (req, res) => { const submolt = await SubmoltService.findByName(req.params.name); const { agent_name, role } = req.body; @@ -147,7 +147,7 @@ router.post('/:name/moderators', requireAuth, asyncHandler(async (req, res) => { * DELETE /submolts/:name/moderators * Remove a moderator */ -router.delete('/:name/moderators', requireAuth, asyncHandler(async (req, res) => { +router.delete('/:name/moderators', requireAuth, requireClaimed, asyncHandler(async (req, res) => { const submolt = await SubmoltService.findByName(req.params.name); const { agent_name } = req.body;