chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:02:44 +08:00
commit 33645b2d9d
144 changed files with 63699 additions and 0 deletions
+107
View File
@@ -0,0 +1,107 @@
import { NextResponse } from 'next/server';
const MUAPI_BASE = 'https://api.muapi.ai';
function getApiKey(request) {
// Only accept x-api-key header. Cookie-based auth is removed for security:
// cookies without HttpOnly flag can be stolen by XSS (CWE-522).
const headerKey = request.headers.get('x-api-key');
return headerKey || null;
}
function cleanHeaders(request) {
const headers = new Headers(request.headers);
headers.delete('host');
headers.delete('connection');
headers.delete('cookie'); // CRITICAL: Stop forwarding browser cookies to MuAPI
return headers;
}
// Build the target URL without a trailing slash when path is empty.
// e.g. GET /api/agents?is_template=true → https://api.muapi.ai/agents?is_template=true
// e.g. GET /api/agents/by-slug/foo → https://api.muapi.ai/agents/by-slug/foo
function buildTargetUrl(pathSegments, search) {
const path = pathSegments.join('/');
const base = `${MUAPI_BASE}/agents`;
return path ? `${base}/${path}${search}` : `${base}${search}`;
}
export async function GET(request, { params }) {
const slug = await params;
const pathSegments = slug.path || [];
const { search } = new URL(request.url);
const targetUrl = buildTargetUrl(pathSegments, search);
const headers = cleanHeaders(request);
const apiKey = getApiKey(request);
// NOTE: credential logging removed for security (CWE-200)
if (apiKey) headers.set('x-api-key', apiKey);
try {
const response = await fetch(targetUrl, { headers, method: 'GET' });
const data = await response.json();
return NextResponse.json(data, { status: response.status });
} catch (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
export async function POST(request, { params }) {
const slug = await params;
const pathSegments = slug.path || [];
const { search } = new URL(request.url);
const targetUrl = buildTargetUrl(pathSegments, search);
const headers = cleanHeaders(request);
const apiKey = getApiKey(request);
// NOTE: credential logging removed for security (CWE-200)
if (apiKey) headers.set('x-api-key', apiKey);
try {
const body = await request.arrayBuffer();
const response = await fetch(targetUrl, { method: 'POST', headers, body });
const data = await response.json();
return NextResponse.json(data, { status: response.status });
} catch (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
export async function DELETE(request, { params }) {
const slug = await params;
const pathSegments = slug.path || [];
const { search } = new URL(request.url);
const targetUrl = buildTargetUrl(pathSegments, search);
const headers = cleanHeaders(request);
const apiKey = getApiKey(request);
if (apiKey) headers.set('x-api-key', apiKey);
try {
const response = await fetch(targetUrl, { method: 'DELETE', headers });
const data = await response.json();
return NextResponse.json(data, { status: response.status });
} catch (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
export async function PUT(request, { params }) {
const slug = await params;
const pathSegments = slug.path || [];
const { search } = new URL(request.url);
const targetUrl = buildTargetUrl(pathSegments, search);
const headers = cleanHeaders(request);
const apiKey = getApiKey(request);
if (apiKey) headers.set('x-api-key', apiKey);
try {
const body = await request.arrayBuffer();
const response = await fetch(targetUrl, { method: 'PUT', headers, body });
const data = await response.json();
return NextResponse.json(data, { status: response.status });
} catch (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
+65
View File
@@ -0,0 +1,65 @@
import { NextResponse } from 'next/server';
const MUAPI_BASE = 'https://api.muapi.ai';
function getApiKey(request) {
const headerKey = request.headers.get('x-api-key');
if (headerKey) return headerKey;
// Cookie-based auth removed for security (CWE-522)
return null;
}
function cleanHeaders(request) {
const headers = new Headers(request.headers);
headers.delete('host');
headers.delete('connection');
headers.delete('cookie');
return headers;
}
// Proxies /api/api/v1/* -> https://api.muapi.ai/api/v1/*
// This is required because the AiAgent library hardcodes a double /api/api
export async function GET(request, { params }) {
const slug = await params;
const pathSegments = slug.path || [];
const path = pathSegments.join('/');
const { search } = new URL(request.url);
const targetUrl = `${MUAPI_BASE}/api/v1/${path}${search}`;
const headers = cleanHeaders(request);
const apiKey = getApiKey(request);
// NOTE: credential logging removed for security (CWE-200)
if (apiKey) headers.set('x-api-key', apiKey);
try {
const response = await fetch(targetUrl, { headers, method: 'GET' });
const data = await response.json();
return NextResponse.json(data, { status: response.status });
} catch (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
export async function POST(request, { params }) {
const slug = await params;
const pathSegments = slug.path || [];
const path = pathSegments.join('/');
const { search } = new URL(request.url);
const targetUrl = `${MUAPI_BASE}/api/v1/${path}${search}`;
const headers = cleanHeaders(request);
const apiKey = getApiKey(request);
if (apiKey) headers.set('x-api-key', apiKey);
try {
const body = await request.arrayBuffer();
const response = await fetch(targetUrl, { method: 'POST', headers, body });
const data = await response.json();
return NextResponse.json(data, { status: response.status });
} catch (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
+142
View File
@@ -0,0 +1,142 @@
import { NextResponse } from 'next/server';
const MUAPI_BASE = 'https://api.muapi.ai';
function getApiKey(request) {
// Only accept x-api-key header. Cookie-based auth is removed for security:
// cookies without HttpOnly flag can be stolen by XSS (CWE-522).
const headerKey = request.headers.get('x-api-key');
return headerKey || null;
}
function cleanHeaders(request) {
const headers = new Headers(request.headers);
headers.delete('host');
headers.delete('connection');
headers.delete('cookie'); // CRITICAL: Stop forwarding browser cookies to MuAPI to avoid auth conflicts
return headers;
}
export async function GET(request, { params }) {
const slug = await params;
const pathSegments = slug.path || [];
const path = pathSegments.join('/');
// Handle alias: get_upload_file -> get_file_upload_url
const effectivePath = path === 'get_upload_file' ? 'get_file_upload_url' : path;
const { search } = new URL(request.url);
const targetUrl = `${MUAPI_BASE}/app/${effectivePath}${search}`;
const headers = cleanHeaders(request);
const apiKey = getApiKey(request);
if (apiKey) headers.set('x-api-key', apiKey);
try {
const response = await fetch(targetUrl, {
headers,
method: 'GET',
});
const data = await response.json();
// SPECIAL CASE: Intercept upload URL and redirect to local binary proxy
if (effectivePath === 'get_file_upload_url' && data.url) {
const originalS3Url = data.url;
// We pass the real S3 URL as a header to our proxy
data.url = `/api/upload-binary`;
// Store target in a temporary way?
// Better: Return the target URL as an extra field that our proxy will look for
data.fields = {
...data.fields,
'x-proxy-target-url': originalS3Url
};
}
return NextResponse.json(data, { status: response.status });
} catch (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
export async function POST(request, { params }) {
const slug = await params;
const pathSegments = slug.path || [];
const path = pathSegments.join('/');
const { search } = new URL(request.url);
const targetUrl = `${MUAPI_BASE}/app/${path}${search}`;
const headers = cleanHeaders(request);
const apiKey = getApiKey(request);
if (apiKey) headers.set('x-api-key', apiKey);
try {
const body = await request.arrayBuffer();
const response = await fetch(targetUrl, {
method: 'POST',
headers,
body
});
const data = await response.json();
return NextResponse.json(data, { status: response.status });
} catch (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
export async function DELETE(request, { params }) {
const slug = await params;
const pathSegments = slug.path || [];
const path = pathSegments.join('/');
const { search } = new URL(request.url);
const targetUrl = `${MUAPI_BASE}/app/${path}${search}`;
const headers = cleanHeaders(request);
const apiKey = getApiKey(request);
if (apiKey) headers.set('x-api-key', apiKey);
try {
const response = await fetch(targetUrl, {
method: 'DELETE',
headers
});
const data = await response.json();
return NextResponse.json(data, { status: response.status });
} catch (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
export async function PUT(request, { params }) {
const slug = await params;
const pathSegments = slug.path || [];
const path = pathSegments.join('/');
const { search } = new URL(request.url);
const targetUrl = `${MUAPI_BASE}/app/${path}${search}`;
const headers = cleanHeaders(request);
const apiKey = getApiKey(request);
if (apiKey) headers.set('x-api-key', apiKey);
try {
const body = await request.arrayBuffer();
const response = await fetch(targetUrl, {
method: 'PUT',
headers,
body
});
const data = await response.json();
return NextResponse.json(data, { status: response.status });
} catch (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
+53
View File
@@ -0,0 +1,53 @@
import { NextResponse } from 'next/server';
import { validateUploadProxyTarget } from '../../../src/lib/uploadProxyTarget';
export async function POST(request) {
try {
const formData = await request.formData();
// Extract the original S3 target URL we injected earlier
const targetUrl = formData.get('x-proxy-target-url');
if (!targetUrl) {
return NextResponse.json({ error: 'Missing proxy target URL' }, { status: 400 });
}
const validatedTarget = validateUploadProxyTarget(targetUrl);
if (!validatedTarget.ok) {
return NextResponse.json(
{ error: 'Invalid upload target', reason: validatedTarget.reason },
{ status: 400 }
);
}
// Reconstruct the FormData for S3 (excluding our internal proxy marker)
const s3FormData = new FormData();
// S3 is very sensitive to field ordering. We must ensure 'file' is likely last
// or at least that all signature fields come before what S3 expects.
// The original library code appends 'file' last, so iterating should preserve that.
for (const [key, value] of formData.entries()) {
if (key !== 'x-proxy-target-url') {
s3FormData.append(key, value);
}
}
// Perform the server-to-server POST to S3
// This bypasses browser CORS/Preflight security entirely
const s3Response = await fetch(validatedTarget.url, {
method: 'POST',
body: s3FormData,
});
if (s3Response.ok || s3Response.status === 204) {
return new Response(null, { status: 204 });
} else {
const errorText = await s3Response.text();
console.error('S3 Proxy Error:', errorText);
return new Response(errorText, { status: s3Response.status });
}
} catch (error) {
console.error('Upload Proxy Exception:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
@@ -0,0 +1,122 @@
import { NextResponse } from 'next/server';
const MUAPI_BASE = 'https://api.muapi.ai';
function getApiKey(request) {
const authHeader = request.headers.get('Authorization');
if (authHeader && authHeader.startsWith('Bearer ')) {
return authHeader.substring(7);
}
const headerKey = request.headers.get('x-api-key');
if (headerKey) return headerKey;
// Cookie-based auth removed for security: no HttpOnly flag exposes key to XSS (CWE-522)
return null;
}
function cleanHeaders(request) {
const headers = new Headers(request.headers);
headers.delete('host');
headers.delete('connection');
headers.delete('cookie');
headers.delete('Authorization');
headers.delete('x-api-key');
return headers;
}
export async function GET(request, { params }) {
const slug = await params;
const pathSegments = slug.path || [];
const path = pathSegments.join('/');
const { search } = new URL(request.url);
const targetUrl = `${MUAPI_BASE}/api/v1/creative-agent/${path}${search}`;
const headers = cleanHeaders(request);
const apiKey = getApiKey(request);
// NOTE: credential logging removed for security (CWE-200)
if (apiKey) headers.set('x-api-key', apiKey);
try {
const response = await fetch(targetUrl, { headers, method: 'GET' });
const data = await response.json();
return NextResponse.json(data, { status: response.status });
} catch (error) {
console.error(`[creative-agent proxy GET ERROR] ${targetUrl}:`, error.message);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
export async function POST(request, { params }) {
const slug = await params;
const pathSegments = slug.path || [];
const path = pathSegments.join('/');
const { search } = new URL(request.url);
const targetUrl = `${MUAPI_BASE}/api/v1/creative-agent/${path}${search}`;
const headers = cleanHeaders(request);
const apiKey = getApiKey(request);
// NOTE: credential logging removed for security (CWE-200)
if (apiKey) headers.set('x-api-key', apiKey);
try {
const body = await request.arrayBuffer();
const response = await fetch(targetUrl, { method: 'POST', headers, body });
const data = await response.json();
return NextResponse.json(data, { status: response.status });
} catch (error) {
console.error(`[creative-agent proxy POST ERROR] ${targetUrl}:`, error.message);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
export async function PATCH(request, { params }) {
const slug = await params;
const pathSegments = slug.path || [];
const path = pathSegments.join('/');
const { search } = new URL(request.url);
const targetUrl = `${MUAPI_BASE}/api/v1/creative-agent/${path}${search}`;
const headers = cleanHeaders(request);
const apiKey = getApiKey(request);
// NOTE: credential logging removed for security (CWE-200)
if (apiKey) headers.set('x-api-key', apiKey);
try {
const body = await request.arrayBuffer();
const response = await fetch(targetUrl, { method: 'PATCH', headers, body });
const data = await response.json();
return NextResponse.json(data, { status: response.status });
} catch (error) {
console.error(`[creative-agent proxy PATCH ERROR] ${targetUrl}:`, error.message);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
export async function DELETE(request, { params }) {
const slug = await params;
const pathSegments = slug.path || [];
const path = pathSegments.join('/');
const { search } = new URL(request.url);
const targetUrl = `${MUAPI_BASE}/api/v1/creative-agent/${path}${search}`;
const headers = cleanHeaders(request);
const apiKey = getApiKey(request);
// NOTE: credential logging removed for security (CWE-200)
if (apiKey) headers.set('x-api-key', apiKey);
try {
const response = await fetch(targetUrl, { method: 'DELETE', headers });
const data = await response.json();
return NextResponse.json(data, { status: response.status });
} catch (error) {
console.error(`[creative-agent proxy DELETE ERROR] ${targetUrl}:`, error.message);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
+40
View File
@@ -0,0 +1,40 @@
import { NextResponse } from 'next/server';
const MUAPI_BASE = 'https://api.muapi.ai';
function getApiKey(request) {
const headerKey = request.headers.get('x-api-key');
if (headerKey) return headerKey;
// Cookie-based auth removed for security (CWE-522)
return null;
}
function cleanHeaders(request) {
const headers = new Headers(request.headers);
headers.delete('host');
headers.delete('connection');
headers.delete('cookie');
return headers;
}
export async function GET(request) {
const { search } = new URL(request.url);
const targetUrl = `${MUAPI_BASE}/app/get_file_upload_url${search}`;
const headers = cleanHeaders(request);
const apiKey = getApiKey(request);
if (apiKey) headers.set('x-api-key', apiKey);
try {
const response = await fetch(targetUrl, {
headers,
method: 'GET',
});
const data = await response.json();
return NextResponse.json(data, { status: response.status });
} catch (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
+46
View File
@@ -0,0 +1,46 @@
import { NextResponse } from 'next/server';
import { validateUploadProxyTarget } from '../../../../src/lib/uploadProxyTarget';
export async function POST(request) {
try {
const formData = await request.formData();
// Extract the original S3 target URL
const targetUrl = formData.get('x-proxy-target-url');
if (!targetUrl) {
return NextResponse.json({ error: 'Missing proxy target URL' }, { status: 400 });
}
const validatedTarget = validateUploadProxyTarget(targetUrl);
if (!validatedTarget.ok) {
return NextResponse.json(
{ error: 'Invalid upload target', reason: validatedTarget.reason },
{ status: 400 }
);
}
const s3FormData = new FormData();
for (const [key, value] of formData.entries()) {
if (key !== 'x-proxy-target-url') {
s3FormData.append(key, value);
}
}
const s3Response = await fetch(validatedTarget.url, {
method: 'POST',
body: s3FormData,
});
if (s3Response.ok || s3Response.status === 204) {
return new Response(null, { status: 204 });
} else {
const errorText = await s3Response.text();
console.error('S3 Proxy Error:', errorText);
return new Response(errorText, { status: s3Response.status });
}
} catch (error) {
console.error('Upload Proxy Exception:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
+134
View File
@@ -0,0 +1,134 @@
import { NextResponse } from 'next/server';
const MUAPI_BASE = 'https://api.muapi.ai';
function getApiKey(request) {
// Only accept x-api-key header. Cookie-based auth is removed for security:
// cookies without HttpOnly flag can be stolen by any XSS (CWE-522).
const headerKey = request.headers.get('x-api-key');
return headerKey || null;
}
function cleanHeaders(request) {
const headers = new Headers(request.headers);
headers.delete('host');
headers.delete('connection');
headers.delete('cookie'); // CRITICAL: Stop forwarding browser cookies to MuAPI to avoid auth conflicts
return headers;
}
export async function GET(request, { params }) {
const slug = await params;
const pathSegments = slug.path || [];
const path = pathSegments.join('/');
const { search } = new URL(request.url);
const targetUrl = `${MUAPI_BASE}/workflow/${path}${search}`;
const headers = cleanHeaders(request);
const apiKey = getApiKey(request);
// NOTE: apiKey is intentionally NOT logged here to prevent credential leakage (CWE-200)
if (apiKey) headers.set('x-api-key', apiKey);
try {
const response = await fetch(targetUrl, {
headers,
method: 'GET',
});
const data = await response.json();
if (path.includes('get-workflow-def')) {
console.log(`[proxy GET] get-workflow-def response: is_owner=${data?.is_owner}, workflow_id=${data?.workflow_id}`);
}
return NextResponse.json(data, { status: response.status });
} catch (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
export async function POST(request, { params }) {
const slug = await params;
const pathSegments = slug.path || [];
const path = pathSegments.join('/');
const { search } = new URL(request.url);
const targetUrl = `${MUAPI_BASE}/workflow/${path}${search}`;
const headers = cleanHeaders(request);
const apiKey = getApiKey(request);
// NOTE: credential logging removed for security (CWE-200)
if (apiKey) headers.set('x-api-key', apiKey);
try {
const body = await request.arrayBuffer();
// Decode body to see what workflow_id is being sent
try {
const parsed = JSON.parse(Buffer.from(body).toString('utf-8'));
console.log(`[proxy POST] body: workflow_id=${parsed.workflow_id}, source_workflow_id=${parsed.source_workflow_id}, name=${parsed.name}`);
} catch(e) { /* ignore decode errors */ }
const response = await fetch(targetUrl, {
method: 'POST',
headers,
body
});
const data = await response.json();
console.log(`[proxy POST] response: status=${response.status}`, JSON.stringify(data).slice(0, 200));
return NextResponse.json(data, { status: response.status });
} catch (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
export async function DELETE(request, { params }) {
const slug = await params;
const pathSegments = slug.path || [];
const path = pathSegments.join('/');
const { search } = new URL(request.url);
const targetUrl = `${MUAPI_BASE}/workflow/${path}${search}`;
const headers = cleanHeaders(request);
const apiKey = getApiKey(request);
if (apiKey) headers.set('x-api-key', apiKey);
try {
const response = await fetch(targetUrl, {
method: 'DELETE',
headers
});
const data = await response.json();
return NextResponse.json(data, { status: response.status });
} catch (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
export async function PUT(request, { params }) {
const slug = await params;
const pathSegments = slug.path || [];
const path = pathSegments.join('/');
const { search } = new URL(request.url);
const targetUrl = `${MUAPI_BASE}/workflow/${path}${search}`;
const headers = cleanHeaders(request);
const apiKey = getApiKey(request);
if (apiKey) headers.set('x-api-key', apiKey);
try {
const body = await request.arrayBuffer();
const response = await fetch(targetUrl, {
method: 'PUT',
headers,
body
});
const data = await response.json();
return NextResponse.json(data, { status: response.status });
} catch (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}