57 lines
4.0 KiB
JavaScript
57 lines
4.0 KiB
JavaScript
// Native Console routes: fixed path -> one owner human-transport call. Never
|
|
// imports a management client, reads proxy settings, or opens a proxy connection.
|
|
const routes = Object.freeze({
|
|
'/api/proxy/accounts': ['GET', 'proxy.accounts', []],
|
|
'/api/proxy/keys': ['GET', 'proxy.keys', []],
|
|
'/api/proxy/plugins': ['GET', 'plugins', []],
|
|
'/api/proxy/plugins/set': ['POST', 'plugins.set', ['revision', 'plugins']],
|
|
'/api/proxy/login/start': ['POST', 'proxy.login.start', ['provider']],
|
|
'/api/proxy/login/submit': ['POST', 'proxy.login.submit', ['redirect_url']],
|
|
'/api/proxy/login/status': ['POST', 'proxy.login.status', ['state']],
|
|
'/api/proxy/login/cancel': ['POST', 'proxy.login.cancel', ['state']],
|
|
'/api/proxy/apikey/add': ['POST', 'proxy.apikey.add', ['provider', 'key']],
|
|
'/api/proxy/priority/set': ['POST', 'proxy.priority.set', ['accountId', 'priority']],
|
|
'/api/proxy/group/models/set': ['POST', 'proxy.group.models.set', ['accountId', 'models']],
|
|
});
|
|
const codes = ['not-configured', 'human-required', 'provider-not-allowed', 'login-state-unknown',
|
|
'callback-url-invalid', 'apikey-invalid', 'priority-invalid', 'priority-unconfirmed', 'priority-write-refused', 'models-invalid', 'models-ambiguous', 'models-write-refused', 'models-unconfirmed', 'proxy-down', 'invalid-request'];
|
|
const json = (res, status, value) => {
|
|
res.writeHead(status, { 'content-type': 'application/json', 'cache-control': 'no-store' }); res.end(JSON.stringify(value));
|
|
};
|
|
export async function proxyHTTP(req, res, { path, call, named, tester, granted, send, origin, user, log }) {
|
|
const route = routes[path];
|
|
const done = (status, value) => {
|
|
// No request body, query, login state, authorization URL or exception text.
|
|
log(`${new Date().toISOString()} ${route?.[0] ?? 'GET'} ${route ? path : '/api/proxy/unknown'} ${status} user=${JSON.stringify(user ?? '')}`);
|
|
return json(res, status, value);
|
|
};
|
|
if (tester || granted || !named) return done(403, { error: 'human-required' });
|
|
if (!route) return done(404, { error: 'not found' });
|
|
if (req.method !== route[0]) return done(405, { error: 'method not allowed' });
|
|
if (req.method === 'POST') {
|
|
if (!send) return done(403, { error: 'read-only' });
|
|
if (req.headersDistinct.origin?.length !== 1 || req.headers.origin !== origin) return done(403, { error: 'cross-origin request refused' });
|
|
if (req.headersDistinct['sec-fetch-site']?.length !== 1 || req.headers['sec-fetch-site'] !== 'same-origin') return done(403, { error: 'cross-site request refused' });
|
|
}
|
|
if (!call) return done(503, { error: 'not-configured' });
|
|
let args = {};
|
|
if (req.method === 'POST') {
|
|
try {
|
|
if ((req.headers['content-type'] ?? '').split(';')[0].trim().toLowerCase() !== 'application/json') throw Error();
|
|
const chunks = []; let size = 0;
|
|
for await (const chunk of req) {
|
|
size += chunk.length; if (size > 16384) throw Error(); chunks.push(chunk);
|
|
}
|
|
args = JSON.parse(Buffer.concat(chunks));
|
|
if (!args || typeof args !== 'object' || Array.isArray(args) || Object.keys(args).length !== route[2].length
|
|
|| route[2].some(k => !Object.hasOwn(args, k))) throw Error();
|
|
} catch { return done(400, { error: 'invalid-request' }); }
|
|
}
|
|
if (path === '/api/proxy/plugins/set' && (!args.plugins || typeof args.plugins !== 'object' || Array.isArray(args.plugins) || Object.keys(args.plugins).length !== 1 || !['harness', 'model-provider'].includes(Object.keys(args.plugins)[0]) || !Array.isArray(Object.values(args.plugins)[0]) || !Object.values(args.plugins)[0].every(name => typeof name === 'string'))) return done(400, { error: 'invalid-request' });
|
|
try { return done(200, await call(route[1], args)); }
|
|
catch (error) {
|
|
const code = codes.includes(error.code) || (path === '/api/proxy/plugins/set' && typeof error.code === 'string' && /^[a-z-]{1,64}$/.test(error.code)) ? error.code : 'outcome-unknown';
|
|
return done(code === 'human-required' ? 403 : ['not-configured', 'proxy-down', 'outcome-unknown'].includes(code) ? 503 : 400, { error: code });
|
|
}
|
|
}
|