fix: backend and discord-gateway improvements
- Update shared database schema - Add shared utils - Refactor backend middleware, auth routes, and dashboard repository - Improve media analysis client with better error handling - Fix searxng search URL construction - Update URL fetcher for robustness Co-authored-by: workflow agents
This commit is contained in:
co-authored by
workflow agents
parent
81a004d250
commit
ade5d6a7c3
@@ -19,6 +19,7 @@ import { createUiStateRouter } from "../modules/ui-state/ui-state.routes.js";
|
||||
import { createGuildsRouter } from "../modules/voice/guilds.routes.js";
|
||||
import { createVoiceRouter } from "../modules/voice/voice.routes.js";
|
||||
import {
|
||||
adminAuth,
|
||||
errorHandler,
|
||||
} from "../shared/middlewares/index.js";
|
||||
import { config } from "../shared/config/index.js";
|
||||
@@ -73,17 +74,18 @@ export function createHttpApp(): Express {
|
||||
app.use("/api", createConfigRouter());
|
||||
app.use("/api", createDashboardRouter());
|
||||
|
||||
// Protected routes — all routes are now public
|
||||
app.use("/api", createMessagesRouter());
|
||||
app.use("/api", createAnalysisRouter());
|
||||
app.use("/api", createMascotChatRouter());
|
||||
app.use("/api", createMediaRouter());
|
||||
app.use("/api", createVoiceRouter());
|
||||
app.use("/api", createRecordingsRouter());
|
||||
app.use("/api", createUiStateRouter());
|
||||
// Protected routes — require admin authentication (X-Admin-Password header)
|
||||
const adminAuthMiddleware = adminAuth(ADMIN_PASSWORD);
|
||||
app.use("/api", adminAuthMiddleware, createMessagesRouter());
|
||||
app.use("/api", adminAuthMiddleware, createAnalysisRouter());
|
||||
app.use("/api", adminAuthMiddleware, createMascotChatRouter());
|
||||
app.use("/api", adminAuthMiddleware, createMediaRouter());
|
||||
app.use("/api", adminAuthMiddleware, createVoiceRouter());
|
||||
app.use("/api", adminAuthMiddleware, createRecordingsRouter());
|
||||
app.use("/api", adminAuthMiddleware, createUiStateRouter());
|
||||
|
||||
// Guilds routes
|
||||
app.use("/api/guilds", createGuildsRouter());
|
||||
app.use("/api/guilds", adminAuthMiddleware, createGuildsRouter());
|
||||
|
||||
// 404 handler
|
||||
app.use((_req: Request, res: Response) => {
|
||||
|
||||
@@ -3,18 +3,22 @@ import { createChildLogger } from "@bete/shared/logger";
|
||||
import type { Request, Response, Router } from "express";
|
||||
import express from "express";
|
||||
import { config } from "../../shared/config/index.js";
|
||||
import { asyncHandler } from "../../shared/middlewares/index.js";
|
||||
import { asyncHandler, rateLimit } from "../../shared/middlewares/index.js";
|
||||
|
||||
const logger = createChildLogger("auth.routes");
|
||||
|
||||
const adminPassword = config.ADMIN_PASSWORD || "admin";
|
||||
|
||||
// Rate limit: max 10 login attempts per IP per 15 minutes
|
||||
const loginRateLimit = rateLimit({ windowMs: 15 * 60 * 1000, max: 10 });
|
||||
|
||||
export function createAuthRouter(): Router {
|
||||
const router = express.Router();
|
||||
|
||||
// POST /api/auth/login
|
||||
router.post(
|
||||
"/auth/login",
|
||||
loginRateLimit,
|
||||
asyncHandler(async (req: Request, res: Response) => {
|
||||
const { password } = req.body as { password?: string };
|
||||
|
||||
|
||||
@@ -43,9 +43,10 @@ export class DashboardRepository {
|
||||
// Top channels by message count
|
||||
const topChannels = await pool.query(`
|
||||
SELECT channel_id,
|
||||
(metadata::jsonb -> 'channel' ->> 'channelName') AS channel_name,
|
||||
COALESCE(NULLIF((metadata::jsonb -> 'channel' ->> 'channelName'), ''), channel_id) AS channel_name,
|
||||
COUNT(*)::int AS message_count
|
||||
FROM messages
|
||||
WHERE metadata IS NOT NULL AND metadata != ''
|
||||
GROUP BY channel_id, (metadata::jsonb -> 'channel' ->> 'channelName')
|
||||
ORDER BY COUNT(*) DESC
|
||||
LIMIT 10
|
||||
|
||||
@@ -50,6 +50,48 @@ export function asyncHandler(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple in-memory rate limiter (no external dependency).
|
||||
* Tracks request counts per IP within a rolling window.
|
||||
* Use for auth endpoints to prevent brute-force attacks.
|
||||
*/
|
||||
export function rateLimit(opts: { windowMs: number; max: number }) {
|
||||
const { windowMs, max } = opts;
|
||||
const hits = new Map<string, { count: number; resetAt: number }>();
|
||||
|
||||
// Periodic cleanup of stale entries to prevent unbounded memory growth
|
||||
const cleanupInterval = setInterval(() => {
|
||||
const now = Date.now();
|
||||
for (const [key, value] of hits) {
|
||||
if (now >= value.resetAt) hits.delete(key);
|
||||
}
|
||||
}, windowMs * 2);
|
||||
cleanupInterval.unref();
|
||||
|
||||
return (req: Request, res: Response, next: NextFunction) => {
|
||||
const ip = req.ip ?? req.socket.remoteAddress ?? "unknown";
|
||||
const now = Date.now();
|
||||
const entry = hits.get(ip);
|
||||
|
||||
if (!entry || now >= entry.resetAt) {
|
||||
hits.set(ip, { count: 1, resetAt: now + windowMs });
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
entry.count++;
|
||||
if (entry.count > max) {
|
||||
res.status(429).json({
|
||||
error: "TOO_MANY_REQUESTS",
|
||||
message: `Rate limit exceeded. Try again in ${Math.ceil((entry.resetAt - now) / 1000)}s.`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that a value is a non-empty string, or throw a descriptive error.
|
||||
* Use for both route params and query string values.
|
||||
|
||||
Reference in New Issue
Block a user