From eb94a2f66970e2c52023e6d4dc2509213db0b2fc Mon Sep 17 00:00:00 2001 From: Nasyarobby Putra Date: Thu, 20 Aug 2026 11:00:47 +0700 Subject: [PATCH] fix(control): allow login when HTTP server is stopped Mount auth routes on the control plane and proxy /api/auth to :8600 in dev:pm2 so the UI can authenticate while the HTTP API process is down. Co-authored-by: Cursor --- packages/server/control.js | 10 +++++++++- packages/server/dev-pm2.mjs | 2 +- packages/server/src/api/auth.js | 18 ++++++++++++++++++ packages/server/start-app.js | 13 ++----------- packages/web/vite.config.js | 5 +++++ 5 files changed, 35 insertions(+), 13 deletions(-) diff --git a/packages/server/control.js b/packages/server/control.js index b89c38a..ac1fd94 100644 --- a/packages/server/control.js +++ b/packages/server/control.js @@ -4,7 +4,7 @@ import cors from "@fastify/cors"; import jwt from "@fastify/jwt"; import { migrate, db } from "./db.js"; import { log, enableLogPersistence, flushLogs } from "./logger.js"; -import { COOKIE } from "./src/api/auth.js"; +import authPlugin, { addApiAuthGuard, COOKIE } from "./src/api/auth.js"; import { clearRestartNeeded, bumpGeneration, @@ -124,6 +124,14 @@ server.decorate("requireAdmin", async function requireAdmin(req, reply) { } }); +await server.register( + async (api) => { + addApiAuthGuard(api, server); + await api.register(authPlugin); + }, + { prefix: "/api" }, +); + /** * @param {number} timeoutMs * @param {string} lockToken diff --git a/packages/server/dev-pm2.mjs b/packages/server/dev-pm2.mjs index 92e71d9..fd9c1da 100644 --- a/packages/server/dev-pm2.mjs +++ b/packages/server/dev-pm2.mjs @@ -108,7 +108,7 @@ run( ["--filter", "@jerapah-flow/web", "dev"], { env: { - // vite.config reads nothing; port is set in vite.config.js + JFLOW_AUTH_PROXY: "http://127.0.0.1:8600", }, }, ); diff --git a/packages/server/src/api/auth.js b/packages/server/src/api/auth.js index 8683230..576d2c8 100644 --- a/packages/server/src/api/auth.js +++ b/packages/server/src/api/auth.js @@ -18,6 +18,24 @@ export function cookieOpts() { }; } +/** + * Require JWT for /api routes except bootstrap, login, and register. + * @param {import("fastify").FastifyInstance} api + * @param {import("fastify").FastifyInstance} root + */ +export function addApiAuthGuard(api, root) { + api.addHook("onRequest", async (req, reply) => { + const raw = (req.url || "").split("?")[0]; + const stripped = raw.replace(/^\/api/, "") || "/"; + const routeUrl = req.routeOptions?.url || stripped; + const open = + OPEN_API_ROUTES.has(`${req.method} ${routeUrl}`) || + OPEN_API_ROUTES.has(`${req.method} ${stripped}`); + if (open) return; + await root.authenticate(req, reply); + }); +} + export function validateCredentials(username, password) { if (!/^[A-Za-z0-9_]{3,32}$/.test(username)) { return "username must be 3-32 letters, numbers, or underscore"; diff --git a/packages/server/start-app.js b/packages/server/start-app.js index a5469f7..5b6a65e 100644 --- a/packages/server/start-app.js +++ b/packages/server/start-app.js @@ -8,7 +8,7 @@ import { migrate, db } from "./db.js"; import { log, enableLogPersistence, flushLogs } from "./logger.js"; import * as store from "./store.js"; import { createRegistry } from "./registry.js"; -import { COOKIE, OPEN_API_ROUTES } from "./src/api/auth.js"; +import { addApiAuthGuard, COOKIE } from "./src/api/auth.js"; import authPlugin from "./src/api/auth.js"; import usersPlugin from "./src/api/users.js"; import scriptsPluginFactory from "./src/api/scripts.js"; @@ -159,16 +159,7 @@ export async function startApp(opts = {}) { if (runApi) { await server.register( async (api) => { - api.addHook("onRequest", async (req, reply) => { - const raw = (req.url || "").split("?")[0]; - const stripped = raw.replace(/^\/api/, "") || "/"; - const routeUrl = req.routeOptions?.url || stripped; - const open = - OPEN_API_ROUTES.has(`${req.method} ${routeUrl}`) || - OPEN_API_ROUTES.has(`${req.method} ${stripped}`); - if (open) return; - await server.authenticate(req, reply); - }); + addApiAuthGuard(api, server); await api.register(authPlugin); await api.register(usersPlugin); await api.register(secretsPlugin); diff --git a/packages/web/vite.config.js b/packages/web/vite.config.js index 8397d27..3288278 100644 --- a/packages/web/vite.config.js +++ b/packages/web/vite.config.js @@ -2,11 +2,16 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import tailwindcss from "@tailwindcss/vite"; +/** In dev:pm2, control (:8600) serves auth so login works when HTTP is stopped. */ +const authProxyTarget = + process.env.JFLOW_AUTH_PROXY ?? "http://127.0.0.1:8700"; + export default defineConfig({ plugins: [react(), tailwindcss()], server: { port: 8500, proxy: { + "/api/auth": { target: authProxyTarget, changeOrigin: true }, "/api": { target: "http://127.0.0.1:8700", changeOrigin: true }, "/admin": { target: "http://127.0.0.1:8700", changeOrigin: true }, "/u": { target: "http://127.0.0.1:8700", changeOrigin: true },