mirror of
https://github.com/fosrl/pangolin.git
synced 2025-05-12 21:30:35 +01:00
Seperate servers
This commit is contained in:
parent
ef7723561e
commit
37f51bec9b
8 changed files with 214 additions and 189 deletions
56
server/apiServer.ts
Normal file
56
server/apiServer.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
import express, { Request, Response } from "express";
|
||||
import cors from "cors";
|
||||
import cookieParser from "cookie-parser";
|
||||
import config from "@server/config";
|
||||
import logger from "@server/logger";
|
||||
import { errorHandlerMiddleware, notFoundMiddleware, rateLimitMiddleware } from "@server/middlewares";
|
||||
import { authenticated, unauthenticated } from "@server/routers/external";
|
||||
import { router as wsRouter, handleWSUpgrade } from "@server/routers/ws";
|
||||
import { logIncomingMiddleware } from "./middlewares/logIncoming";
|
||||
|
||||
const dev = process.env.ENVIRONMENT !== "prod";
|
||||
const externalPort = config.server.external_port;
|
||||
|
||||
export function createApiServer() {
|
||||
const apiServer = express();
|
||||
|
||||
// Middleware setup
|
||||
apiServer.set("trust proxy", 1);
|
||||
apiServer.use(cors());
|
||||
apiServer.use(cookieParser());
|
||||
apiServer.use(express.json());
|
||||
|
||||
if (!dev) {
|
||||
apiServer.use(
|
||||
rateLimitMiddleware({
|
||||
windowMin: config.rate_limit.window_minutes,
|
||||
max: config.rate_limit.max_requests,
|
||||
type: "IP_ONLY",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// API routes
|
||||
const prefix = `/api/v1`;
|
||||
apiServer.use(logIncomingMiddleware);
|
||||
apiServer.use(prefix, unauthenticated);
|
||||
apiServer.use(prefix, authenticated);
|
||||
|
||||
// WebSocket routes
|
||||
apiServer.use(`/ws`, wsRouter);
|
||||
|
||||
// Error handling
|
||||
apiServer.use(notFoundMiddleware);
|
||||
apiServer.use(errorHandlerMiddleware);
|
||||
|
||||
// Create HTTP server
|
||||
const httpServer = apiServer.listen(externalPort, (err?: any) => {
|
||||
if (err) throw err;
|
||||
logger.info(`API server is running on http://localhost:${externalPort}`);
|
||||
});
|
||||
|
||||
// Handle WebSocket upgrades
|
||||
handleWSUpgrade(httpServer);
|
||||
|
||||
return httpServer;
|
||||
}
|
|
@ -21,6 +21,7 @@ const environmentSchema = z.object({
|
|||
server: z.object({
|
||||
external_port: portSchema,
|
||||
internal_port: portSchema,
|
||||
next_port: portSchema,
|
||||
internal_hostname: z.string(),
|
||||
secure_cookies: z.boolean(),
|
||||
signup_secret: z.string().optional(),
|
||||
|
|
134
server/index.ts
134
server/index.ts
|
@ -1,107 +1,35 @@
|
|||
import config from "@server/config";
|
||||
import express, { Request, Response } from "express";
|
||||
import next from "next";
|
||||
import { parse } from "url";
|
||||
import logger from "@server/logger";
|
||||
import helmet from "helmet";
|
||||
import cors from "cors";
|
||||
import {
|
||||
errorHandlerMiddleware,
|
||||
notFoundMiddleware,
|
||||
rateLimitMiddleware,
|
||||
} from "@server/middlewares";
|
||||
import internal from "@server/routers/internal";
|
||||
import { authenticated, unauthenticated } from "@server/routers/external";
|
||||
import { router as wsRouter, handleWSUpgrade } from "@server/routers/ws";
|
||||
import cookieParser from "cookie-parser";
|
||||
import { User, UserOrg } from "@server/db/schema";
|
||||
import { ensureActions } from "./db/ensureActions";
|
||||
import { logIncomingMiddleware } from "./middlewares/logIncoming";
|
||||
import { createApiServer } from "./apiServer";
|
||||
import { createNextServer } from "./nextServer";
|
||||
import { createInternalServer } from "./internalServer";
|
||||
import { User, UserOrg } from "./db/schema";
|
||||
|
||||
const dev = process.env.ENVIRONMENT !== "prod";
|
||||
async function startServers() {
|
||||
await ensureActions();
|
||||
|
||||
// Start all servers
|
||||
const apiServer = createApiServer();
|
||||
const nextServer = await createNextServer();
|
||||
const internalServer = createInternalServer();
|
||||
|
||||
const app = next({ dev });
|
||||
const handle = app.getRequestHandler();
|
||||
|
||||
const externalPort = config.server.external_port;
|
||||
const internalPort = config.server.internal_port;
|
||||
|
||||
app.prepare().then(() => {
|
||||
ensureActions(); // This loads the actions into the database
|
||||
|
||||
// External server
|
||||
const externalServer = express();
|
||||
externalServer.set("trust proxy", 1);
|
||||
|
||||
// externalServer.use(helmet()); // Disabled because causes issues with Next.js
|
||||
externalServer.use(cors());
|
||||
externalServer.use(cookieParser());
|
||||
externalServer.use(express.json());
|
||||
if (!dev) {
|
||||
externalServer.use(
|
||||
rateLimitMiddleware({
|
||||
windowMin: config.rate_limit.window_minutes,
|
||||
max: config.rate_limit.max_requests,
|
||||
type: "IP_ONLY",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const prefix = `/api/v1`;
|
||||
externalServer.use(logIncomingMiddleware);
|
||||
externalServer.use(prefix, unauthenticated);
|
||||
externalServer.use(prefix, authenticated);
|
||||
// externalServer.use(`${prefix}/ws`, wsRouter);
|
||||
|
||||
externalServer.use(notFoundMiddleware);
|
||||
|
||||
// We are using NEXT from here on
|
||||
externalServer.all("*", (req: Request, res: Response) => {
|
||||
const parsedUrl = parse(req.url!, true);
|
||||
handle(req, res, parsedUrl);
|
||||
});
|
||||
|
||||
const httpServer = externalServer.listen(externalPort, (err?: any) => {
|
||||
if (err) throw err;
|
||||
logger.info(
|
||||
`Main server is running on http://localhost:${externalPort}`
|
||||
);
|
||||
});
|
||||
|
||||
// handleWSUpgrade(httpServer);
|
||||
|
||||
externalServer.use(errorHandlerMiddleware);
|
||||
|
||||
// Internal server
|
||||
const internalServer = express();
|
||||
|
||||
internalServer.use(helmet());
|
||||
internalServer.use(cors());
|
||||
internalServer.use(cookieParser());
|
||||
internalServer.use(express.json());
|
||||
|
||||
internalServer.use(prefix, internal);
|
||||
|
||||
internalServer.listen(internalPort, (err?: any) => {
|
||||
if (err) throw err;
|
||||
logger.info(
|
||||
`Internal server is running on http://localhost:${internalPort}`
|
||||
);
|
||||
});
|
||||
|
||||
internalServer.use(notFoundMiddleware);
|
||||
internalServer.use(errorHandlerMiddleware);
|
||||
});
|
||||
|
||||
declare global {
|
||||
// TODO: eventually make seperate types that extend express.Request
|
||||
namespace Express {
|
||||
interface Request {
|
||||
user?: User;
|
||||
userOrg?: UserOrg;
|
||||
userOrgRoleId?: number;
|
||||
userOrgId?: string;
|
||||
userOrgIds?: string[];
|
||||
}
|
||||
}
|
||||
return {
|
||||
apiServer,
|
||||
nextServer,
|
||||
internalServer
|
||||
};
|
||||
}
|
||||
|
||||
// Types
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
user?: User;
|
||||
userOrg?: UserOrg;
|
||||
userOrgRoleId?: number;
|
||||
userOrgId?: string;
|
||||
userOrgIds?: string[];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
startServers().catch(console.error);
|
||||
|
|
32
server/internalServer.ts
Normal file
32
server/internalServer.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import express from "express";
|
||||
import helmet from "helmet";
|
||||
import cors from "cors";
|
||||
import cookieParser from "cookie-parser";
|
||||
import config from "@server/config";
|
||||
import logger from "@server/logger";
|
||||
import { errorHandlerMiddleware, notFoundMiddleware } from "@server/middlewares";
|
||||
import internal from "@server/routers/internal";
|
||||
|
||||
const internalPort = config.server.internal_port;
|
||||
|
||||
export function createInternalServer() {
|
||||
const internalServer = express();
|
||||
|
||||
internalServer.use(helmet());
|
||||
internalServer.use(cors());
|
||||
internalServer.use(cookieParser());
|
||||
internalServer.use(express.json());
|
||||
|
||||
const prefix = `/api/v1`;
|
||||
internalServer.use(prefix, internal);
|
||||
|
||||
internalServer.use(notFoundMiddleware);
|
||||
internalServer.use(errorHandlerMiddleware);
|
||||
|
||||
internalServer.listen(internalPort, (err?: any) => {
|
||||
if (err) throw err;
|
||||
logger.info(`Internal server is running on http://localhost:${internalPort}`);
|
||||
});
|
||||
|
||||
return internalServer;
|
||||
}
|
29
server/nextServer.ts
Normal file
29
server/nextServer.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
import next from "next";
|
||||
import express from "express";
|
||||
import { parse } from "url";
|
||||
import logger from "@server/logger";
|
||||
import config from "@server/config";
|
||||
|
||||
const nextPort = config.server.next_port;
|
||||
|
||||
export async function createNextServer() {
|
||||
// const app = next({ dev });
|
||||
const app = next({ dev: process.env.ENVIRONMENT !== "prod" });
|
||||
const handle = app.getRequestHandler();
|
||||
|
||||
await app.prepare();
|
||||
|
||||
const nextServer = express();
|
||||
|
||||
nextServer.all("*", (req, res) => {
|
||||
const parsedUrl = parse(req.url!, true);
|
||||
return handle(req, res, parsedUrl);
|
||||
});
|
||||
|
||||
nextServer.listen(nextPort, (err?: any) => {
|
||||
if (err) throw err;
|
||||
logger.info(`Next.js server is running on http://localhost:${nextPort}`);
|
||||
});
|
||||
|
||||
return nextServer;
|
||||
}
|
|
@ -17,7 +17,6 @@ interface WebSocketRequest extends IncomingMessage {
|
|||
|
||||
interface AuthenticatedWebSocket extends WebSocket {
|
||||
newt?: Newt;
|
||||
isAlive?: boolean;
|
||||
}
|
||||
|
||||
interface TokenPayload {
|
||||
|
@ -124,77 +123,23 @@ const verifyToken = async (token: string): Promise<TokenPayload | null> => {
|
|||
|
||||
return { newt: existingNewt[0], session };
|
||||
} catch (error) {
|
||||
console.error("Token verification failed:", error);
|
||||
logger.error("Token verification failed:", error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// Router endpoint (unchanged)
|
||||
router.get("/ws", (req: Request, res: Response) => {
|
||||
res.status(200).send("WebSocket endpoint");
|
||||
});
|
||||
const setupConnection = (ws: AuthenticatedWebSocket, newt: Newt): void => {
|
||||
logger.info("Establishing websocket connection");
|
||||
|
||||
// WebSocket upgrade handler
|
||||
const handleWSUpgrade = (server: HttpServer): void => {
|
||||
server.on("upgrade", async (request: WebSocketRequest, socket: Socket, head: Buffer) => {
|
||||
try {
|
||||
const token = request.url?.includes("?")
|
||||
? new URLSearchParams(request.url.split("?")[1]).get("token") || ""
|
||||
: request.headers["sec-websocket-protocol"];
|
||||
|
||||
if (!token) {
|
||||
socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
const tokenPayload = await verifyToken(token);
|
||||
if (!tokenPayload) {
|
||||
socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
request.token = token;
|
||||
|
||||
wss.handleUpgrade(request, socket, head, (ws: AuthenticatedWebSocket) => {
|
||||
ws.newt = tokenPayload.newt;
|
||||
ws.isAlive = true;
|
||||
wss.emit("connection", ws, request);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Upgrade error:", error);
|
||||
socket.write("HTTP/1.1 500 Internal Server Error\r\n\r\n");
|
||||
socket.destroy();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// WebSocket connection handler
|
||||
wss.on("connection", (ws: AuthenticatedWebSocket, request: WebSocketRequest) => {
|
||||
const newtId = ws.newt?.newtId;
|
||||
if (!newtId) {
|
||||
console.error("Connection attempt without newt ID");
|
||||
if (!newt) {
|
||||
logger.error("Connection attempt without newt");
|
||||
return ws.terminate();
|
||||
}
|
||||
|
||||
ws.newt = newt;
|
||||
|
||||
// Add client to tracking
|
||||
addClient(newtId, ws);
|
||||
|
||||
// Set up ping-pong for connection health check
|
||||
const pingInterval = setInterval(() => {
|
||||
if (ws.isAlive === false) {
|
||||
clearInterval(pingInterval);
|
||||
removeClient(newtId, ws);
|
||||
return ws.terminate();
|
||||
}
|
||||
ws.isAlive = false;
|
||||
ws.ping();
|
||||
}, 30000);
|
||||
|
||||
ws.on("pong", () => {
|
||||
ws.isAlive = true;
|
||||
});
|
||||
addClient(newt.newtId, ws);
|
||||
|
||||
ws.on("message", async (data) => {
|
||||
try {
|
||||
|
@ -226,7 +171,7 @@ wss.on("connection", (ws: AuthenticatedWebSocket, request: WebSocketRequest) =>
|
|||
if (response) {
|
||||
if (response.broadcast) {
|
||||
// Broadcast to all clients except sender if specified
|
||||
broadcastToAllExcept(response.message, response.excludeSender ? newtId : undefined);
|
||||
broadcastToAllExcept(response.message, response.excludeSender ? newt.newtId : undefined);
|
||||
} else if (response.targetNewtId) {
|
||||
// Send to specific client if targetNewtId is provided
|
||||
sendToClient(response.targetNewtId, response.message);
|
||||
|
@ -235,9 +180,9 @@ wss.on("connection", (ws: AuthenticatedWebSocket, request: WebSocketRequest) =>
|
|||
ws.send(JSON.stringify(response.message));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error("Message handling error:", error);
|
||||
logger.error("Message handling error:", error);
|
||||
ws.send(JSON.stringify({
|
||||
type: "error",
|
||||
data: {
|
||||
|
@ -247,18 +192,58 @@ wss.on("connection", (ws: AuthenticatedWebSocket, request: WebSocketRequest) =>
|
|||
}));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
ws.on("close", () => {
|
||||
clearInterval(pingInterval);
|
||||
removeClient(newtId, ws);
|
||||
logger.info(`Client disconnected - Newt ID: ${newtId}`);
|
||||
removeClient(newt.newtId, ws);
|
||||
logger.info(`Client disconnected - Newt ID: ${newt.newtId}`);
|
||||
});
|
||||
|
||||
ws.on("error", (error: Error) => {
|
||||
logger.error(`WebSocket error for Newt ID ${newt.newtId}:`, error);
|
||||
});
|
||||
|
||||
ws.on("error", (error: Error) => {
|
||||
console.error(`WebSocket error for Newt ID ${newtId}:`, error);
|
||||
});
|
||||
logger.info(`WebSocket connection established - Newt ID: ${newt.newtId}`);
|
||||
};
|
||||
|
||||
// Router endpoint (unchanged)
|
||||
router.get("/ws", (req: Request, res: Response) => {
|
||||
res.status(200).send("WebSocket endpoint");
|
||||
});
|
||||
|
||||
// WebSocket upgrade handler
|
||||
const handleWSUpgrade = (server: HttpServer): void => {
|
||||
server.on("upgrade", async (request: WebSocketRequest, socket: Socket, head: Buffer) => {
|
||||
try {
|
||||
const token = request.url?.includes("?")
|
||||
? new URLSearchParams(request.url.split("?")[1]).get("token") || ""
|
||||
: request.headers["sec-websocket-protocol"];
|
||||
|
||||
if (!token) {
|
||||
logger.warn("Unauthorized connection attempt: no token...");
|
||||
socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
const tokenPayload = await verifyToken(token);
|
||||
if (!tokenPayload) {
|
||||
logger.warn("Unauthorized connection attempt: invalid token...");
|
||||
socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
wss.handleUpgrade(request, socket, head, (ws: AuthenticatedWebSocket) => {
|
||||
setupConnection(ws, tokenPayload.newt);
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("WebSocket upgrade error:", error);
|
||||
socket.write("HTTP/1.1 500 Internal Server Error\r\n\r\n");
|
||||
socket.destroy();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export {
|
||||
router,
|
||||
handleWSUpgrade,
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
"use client";
|
||||
|
||||
export function NewtConfig() {
|
||||
const config = `curl -fsSL https://get.docker.com -o get-docker.sh
|
||||
sh get-docker.sh`;
|
||||
|
||||
return (
|
||||
<pre className="mt-2 w-full rounded-md bg-slate-950 p-4 overflow-x-auto">
|
||||
<code className="text-white whitespace-pre-wrap">{config}</code>
|
||||
</pre>
|
||||
);
|
||||
}
|
|
@ -174,7 +174,13 @@ Endpoint = ${siteDefaults.endpoint}:${siteDefaults.listenPort}
|
|||
PersistentKeepalive = 5`
|
||||
: "";
|
||||
|
||||
const newtConfig = `newt --id ${siteDefaults?.newtId} --secret ${siteDefaults?.newtSecret}`;
|
||||
// am I at http or https?
|
||||
let proto = "http:";
|
||||
if (typeof window !== "undefined") {
|
||||
proto = window.location.protocol;
|
||||
}
|
||||
|
||||
const newtConfig = `newt --id ${siteDefaults?.newtId} --secret ${siteDefaults?.newtSecret} --endpoint ${proto}//${siteDefaults?.endpoint}`;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
Loading…
Reference in a new issue