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 <cursoragent@cursor.com>
This commit is contained in:
2026-08-20 11:00:47 +07:00
co-authored by Cursor
parent 21a31b0b54
commit eb94a2f669
5 changed files with 35 additions and 13 deletions
+9 -1
View File
@@ -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
+1 -1
View File
@@ -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",
},
},
);
+18
View File
@@ -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";
+2 -11
View File
@@ -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);
+5
View File
@@ -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 },