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:
@@ -4,7 +4,7 @@ import cors from "@fastify/cors";
|
|||||||
import jwt from "@fastify/jwt";
|
import jwt from "@fastify/jwt";
|
||||||
import { migrate, db } from "./db.js";
|
import { migrate, db } from "./db.js";
|
||||||
import { log, enableLogPersistence, flushLogs } from "./logger.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 {
|
import {
|
||||||
clearRestartNeeded,
|
clearRestartNeeded,
|
||||||
bumpGeneration,
|
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 {number} timeoutMs
|
||||||
* @param {string} lockToken
|
* @param {string} lockToken
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ run(
|
|||||||
["--filter", "@jerapah-flow/web", "dev"],
|
["--filter", "@jerapah-flow/web", "dev"],
|
||||||
{
|
{
|
||||||
env: {
|
env: {
|
||||||
// vite.config reads nothing; port is set in vite.config.js
|
JFLOW_AUTH_PROXY: "http://127.0.0.1:8600",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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) {
|
export function validateCredentials(username, password) {
|
||||||
if (!/^[A-Za-z0-9_]{3,32}$/.test(username)) {
|
if (!/^[A-Za-z0-9_]{3,32}$/.test(username)) {
|
||||||
return "username must be 3-32 letters, numbers, or underscore";
|
return "username must be 3-32 letters, numbers, or underscore";
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { migrate, db } from "./db.js";
|
|||||||
import { log, enableLogPersistence, flushLogs } from "./logger.js";
|
import { log, enableLogPersistence, flushLogs } from "./logger.js";
|
||||||
import * as store from "./store.js";
|
import * as store from "./store.js";
|
||||||
import { createRegistry } from "./registry.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 authPlugin from "./src/api/auth.js";
|
||||||
import usersPlugin from "./src/api/users.js";
|
import usersPlugin from "./src/api/users.js";
|
||||||
import scriptsPluginFactory from "./src/api/scripts.js";
|
import scriptsPluginFactory from "./src/api/scripts.js";
|
||||||
@@ -159,16 +159,7 @@ export async function startApp(opts = {}) {
|
|||||||
if (runApi) {
|
if (runApi) {
|
||||||
await server.register(
|
await server.register(
|
||||||
async (api) => {
|
async (api) => {
|
||||||
api.addHook("onRequest", async (req, reply) => {
|
addApiAuthGuard(api, server);
|
||||||
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);
|
|
||||||
});
|
|
||||||
await api.register(authPlugin);
|
await api.register(authPlugin);
|
||||||
await api.register(usersPlugin);
|
await api.register(usersPlugin);
|
||||||
await api.register(secretsPlugin);
|
await api.register(secretsPlugin);
|
||||||
|
|||||||
@@ -2,11 +2,16 @@ import { defineConfig } from "vite";
|
|||||||
import react from "@vitejs/plugin-react";
|
import react from "@vitejs/plugin-react";
|
||||||
import tailwindcss from "@tailwindcss/vite";
|
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({
|
export default defineConfig({
|
||||||
plugins: [react(), tailwindcss()],
|
plugins: [react(), tailwindcss()],
|
||||||
server: {
|
server: {
|
||||||
port: 8500,
|
port: 8500,
|
||||||
proxy: {
|
proxy: {
|
||||||
|
"/api/auth": { target: authProxyTarget, changeOrigin: true },
|
||||||
"/api": { target: "http://127.0.0.1:8700", changeOrigin: true },
|
"/api": { target: "http://127.0.0.1:8700", changeOrigin: true },
|
||||||
"/admin": { 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 },
|
"/u": { target: "http://127.0.0.1:8700", changeOrigin: true },
|
||||||
|
|||||||
Reference in New Issue
Block a user