feat: ComfyUI embedded dev setup + proxy auth with downstream keys

- COMFYUI_BASE configurable via env var (default 127.0.0.1:8188)
- All /api/comfyui/ routes protected by downstream API Key or admin token
- docker/Dockerfile.comfyui + docker-compose.comfyui.yml for dev
- npm scripts: comfyui:install, comfyui:start, comfyui:stop
- .gitignore / .env.example updated
This commit is contained in:
boos
2026-05-17 13:04:46 +08:00
parent 2fe8d8528a
commit ad0bb86e31
11 changed files with 308 additions and 7 deletions
+1 -1
View File
@@ -5,7 +5,7 @@ const DESKTOP_HEALTH_ROUTE = '/api/desktop/health';
export function isPublicApiRoute(url: string): boolean {
return url === DESKTOP_HEALTH_ROUTE
|| url.startsWith('/api/oauth/callback/')
|| url.startsWith('/api/comfyui/proxy/')
|| url.startsWith('/api/comfyui/')
// User SaaS routes (JWT auth, not admin token)
|| url === '/api/users/register'
|| url === '/api/users/login'
+41 -6
View File
@@ -1,7 +1,34 @@
import { FastifyInstance } from 'fastify';
import { fetch } from 'undici';
import { authorizeDownstreamToken } from '../../services/downstreamApiKeyService.js';
import { config } from '../../config.js';
const COMFYUI_BASE = 'http://127.0.0.1:8188';
const COMFYUI_BASE = process.env.COMFYUI_BASE || 'http://127.0.0.1:8188';
/** 同时支持管理员 token 和下游 API Key 认证 */
async function comfyuiAuth(request: { headers: { authorization?: string } }): Promise<{ ok: boolean; error?: string; code?: number }> {
const auth = (request.headers.authorization || '').trim();
if (!auth) {
return { ok: false, error: 'Missing Authorization header', code: 401 };
}
const token = auth.startsWith('Bearer ') ? auth.slice(7).trim() : auth;
// 1. 管理员 token
if (token === config.authToken) {
return { ok: true };
}
// 2. 下游 API Key (sk-xxx)
if (token.startsWith('sk-')) {
const result = await authorizeDownstreamToken(token);
if (result.ok) {
return { ok: true };
}
return { ok: false, error: result.error, code: result.statusCode };
}
return { ok: false, error: 'Invalid token', code: 403 };
}
/** Rebrand ComfyUI HTML to show BoosAPI instead */
function rebrandHtml(html: string): string {
@@ -17,8 +44,16 @@ function rewriteProxyUrls(html: string): string {
}
export async function comfyuiRoutes(app: FastifyInstance) {
// Auth preHandler: prompt, history, queue, view 需要认证
const requireAuth = async (request: { headers: { authorization?: string } }, reply: any) => {
const result = await comfyuiAuth(request);
if (!result.ok) {
reply.code(result.code || 403).send({ error: result.error });
}
};
// Proxy: POST /api/comfyui/prompt -> ComfyUI /prompt
app.post('/api/comfyui/prompt', async (request, reply) => {
app.post('/api/comfyui/prompt', { preHandler: requireAuth }, async (request, reply) => {
try {
const body = typeof request.body === 'object' ? JSON.stringify(request.body) : '{}';
const resp = await fetch(`${COMFYUI_BASE}/prompt`, {
@@ -34,7 +69,7 @@ export async function comfyuiRoutes(app: FastifyInstance) {
});
// Proxy: GET /api/comfyui/history -> ComfyUI /history
app.get('/api/comfyui/history', async (_request, reply) => {
app.get('/api/comfyui/history', { preHandler: requireAuth }, async (_request, reply) => {
try {
const resp = await fetch(`${COMFYUI_BASE}/history`);
const text = await resp.text();
@@ -45,7 +80,7 @@ export async function comfyuiRoutes(app: FastifyInstance) {
});
// Proxy: GET /api/comfyui/history/:id -> ComfyUI /history/{id}
app.get<{ Params: { id: string } }>('/api/comfyui/history/:id', async (request, reply) => {
app.get<{ Params: { id: string } }>('/api/comfyui/history/:id', { preHandler: requireAuth }, async (request, reply) => {
try {
const resp = await fetch(`${COMFYUI_BASE}/history/${encodeURIComponent(request.params.id)}`);
const text = await resp.text();
@@ -56,7 +91,7 @@ export async function comfyuiRoutes(app: FastifyInstance) {
});
// Proxy: GET /api/comfyui/queue -> ComfyUI /queue
app.get('/api/comfyui/queue', async (_request, reply) => {
app.get('/api/comfyui/queue', { preHandler: requireAuth }, async (_request, reply) => {
try {
const resp = await fetch(`${COMFYUI_BASE}/queue`);
const text = await resp.text();
@@ -67,7 +102,7 @@ export async function comfyuiRoutes(app: FastifyInstance) {
});
// Proxy: GET /api/comfyui/view -> ComfyUI /view (image output, preserve query params)
app.get('/api/comfyui/view', async (request, reply) => {
app.get('/api/comfyui/view', { preHandler: requireAuth }, async (request, reply) => {
try {
const qs = request.url.includes('?') ? request.url.substring(request.url.indexOf('?')) : '';
const resp = await fetch(`${COMFYUI_BASE}/view${qs}`);