Revert "feat: migrate frontend to Astro + expand AI moderation + backend admin/runtime config"

This reverts commit d59b59a7a7.
This commit is contained in:
asepharyana
2026-07-02 03:54:44 +07:00
parent 9636087e99
commit 7efaf00c93
91 changed files with 670 additions and 11161 deletions
+9 -115
View File
@@ -1,4 +1,3 @@
import cors from "cors";
import { createChildLogger } from "@bete/shared/logger";
import express, {
type Express,
@@ -7,8 +6,6 @@ import express, {
type Response,
} from "express";
import helmet from "helmet";
import rateLimit from "express-rate-limit";
import { createAdminRouter } from "../modules/admin/admin.routes.js";
import { createAnalysisRouter } from "../modules/analysis/analysis.routes.js";
import { createAuthRouter } from "../modules/auth/auth.routes.js";
import { createConfigRouter } from "../modules/config/config.routes.js";
@@ -22,47 +19,14 @@ 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 {
sessionAuth,
errorHandler,
} from "../shared/middlewares/index.js";
import { config } from "../shared/config/index.js";
import { isDashboardPublic } from "../shared/config/runtime.js";
const ADMIN_PASSWORD = config.ADMIN_PASSWORD;
const ADMIN_PASSWORD = config.ADMIN_PASSWORD || "admin";
const logger = createChildLogger("http.app");
// Whitelist of GET endpoints allowed in public (unauthenticated) mode.
// All other GET requests require auth even when DASHBOARD_IS_PUBLIC is true.
const PUBLIC_GET_PATHS = [
"/api/dashboard/stats",
"/api/dashboard/users",
"/api/dashboard/channels",
"/api/ui-state",
"/api/media/status",
"/api/mascot/chat/history",
"/api/messages",
"/api/analysis",
"/api/recordings",
"/api/voice",
];
/**
* Dynamic auth guard — checks runtime DASHBOARD_IS_PUBLIC setting for every request.
* In public mode: only whitelisted GET paths pass through; everything else requires auth.
* In private mode: all routes require auth.
*/
function protectedRoute(req: Request, res: Response, next: NextFunction) {
if (req.method === "GET" && isDashboardPublic()) {
const matched = PUBLIC_GET_PATHS.some(
(path) => req.path === path || req.path.startsWith(path + "/"),
);
if (matched) {
return next();
}
}
return sessionAuth(ADMIN_PASSWORD)(req, res, next);
}
export function createHttpApp(): Express {
const app = express();
@@ -73,71 +37,10 @@ export function createHttpApp(): Express {
}),
);
// CORS — allow known frontend origins
// Security note: strict origin whitelist prevents unauthorized cross-origin
// access. In production, ensure only legitimate frontend domains are listed.
// Development: local Vite preview ports
// Production: nginx reverse-proxy serves both on the same domain,
// but we whitelist them for browser preflights too.
const allowedOrigins = [
"http://localhost:5173", // Vite dev server
"http://localhost:4173", // Vite preview server
"http://localhost:3000", // Vite preview (alternate)
"http://localhost:3001", // Backend direct (dev)
"https://imphnen.asepharyana.my.id",
"https://imphnen.asepharyana.tech",
"https://imphnen.asepharyana.web.id",
];
app.use(
cors({
origin: (origin, callback) => {
// Allow requests with no origin (server-to-server, curl, etc.)
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error(`Origin ${origin} not allowed by CORS`));
}
},
credentials: true,
methods: ["GET", "POST", "PATCH", "DELETE", "OPTIONS"],
allowedHeaders: ["Content-Type", "Authorization", "X-Admin-Password"],
maxAge: 86400, // 24 hours — browser can cache preflight
}),
);
// CSRF TODO: state-changing endpoints (POST, PATCH, DELETE) should
// implement CSRF protection (e.g., double-submit cookie pattern or
// SameSite=Strict + custom header check) before deploying to production.
// Body parsing
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Global rate limiter — pertahanan lapisan pertama terhadap abuse
// Endpoint login (/api/auth/login) punya rate limiter sendiri yang lebih ketat
// TODO: The global limiter is currently applied only at /api/ prefix (line below),
// which leaves non-/api/ paths unguarded. Consider applying a lighter limiter
// to all paths or ensure nginx handles upstream rate limiting in production.
const globalLimiter = rateLimit({
windowMs: 15 * 1000, // 15 seconds
max: 200,
standardHeaders: true,
legacyHeaders: false,
skip: (req) => {
// Skip health checks and WebSocket upgrade requests
if (req.path === "/api/health" || req.path === "/health") return true;
if (req.headers.upgrade === "websocket") return true;
return false;
},
message: {
error: "TOO_MANY_REQUESTS",
message: "Too many requests, please slow down",
},
});
app.use("/api/", globalLimiter);
// Request logging
app.use((req: Request, res: Response, next: NextFunction) => {
if (req.path.startsWith("/api/")) {
@@ -160,35 +63,26 @@ export function createHttpApp(): Express {
next();
});
// Open endpoints (no auth required)
// Health check (no auth required)
app.use("/api", createHealthRouter());
// Auth (no auth required)
app.use("/api", createAuthRouter());
// Public read-only endpoints
app.use("/api", createConfigRouter());
// Admin endpoints — always require auth (manage settings, etc.)
// NOTE: createAdminRouter() sudah punya sessionAuth middleware internal,
// jadi tidak perlu middleware terpisah di sini.
app.use("/api", createAdminRouter());
// Protected routes — guarded by runtime DASHBOARD_IS_PUBLIC setting
// Public mode: GET is read-only, mutations require admin password
// Private mode: everything requires admin password
app.use("/api/dashboard", protectedRoute);
app.use("/api", createDashboardRouter());
app.use("/api", protectedRoute);
// Protected routes — all routes are now public
app.use("/api", createMessagesRouter());
app.use("/api", createAnalysisRouter());
app.use("/api", createMascotChatRouter());
// These routers are already guarded by the protectedRoute above
app.use("/api", createMediaRouter());
app.use("/api", createVoiceRouter());
app.use("/api", createRecordingsRouter());
app.use("/api", createUiStateRouter());
// Guilds routes — always protected (even in public mode)
app.use("/api/guilds", sessionAuth(ADMIN_PASSWORD));
// Guilds routes
app.use("/api/guilds", createGuildsRouter());
// 404 handler