feat: 更新助手配置,添加应用ID和URL,优化身份验证和代理逻辑
This commit is contained in:
@@ -68,46 +68,92 @@ export const initConfig = (configRootPath: string) => {
|
|||||||
export type ReturnInitConfigType = ReturnType<typeof initConfig>;
|
export type ReturnInitConfigType = ReturnType<typeof initConfig>;
|
||||||
|
|
||||||
type AuthPermission = {
|
type AuthPermission = {
|
||||||
type?: 'auth-proxy' | 'public' | 'private' | 'project';
|
share?: 'public' | 'private' | 'protected';
|
||||||
username?: string; // 用户名
|
username?: string; // 用户名
|
||||||
admin?: string[];
|
admin?: string[];
|
||||||
};
|
};
|
||||||
export type AssistantConfigData = {
|
export type AssistantConfigData = {
|
||||||
pageApi?: string; // https://kevisual.cn
|
app?: {
|
||||||
|
/**
|
||||||
|
* 应用ID, 唯一标识,识别是那个设备
|
||||||
|
*/
|
||||||
|
id?: string;
|
||||||
|
/**
|
||||||
|
* 应用地址
|
||||||
|
*/
|
||||||
|
url?: string;
|
||||||
|
}
|
||||||
token?: string;
|
token?: string;
|
||||||
registry?: string; // https://kevisual.cn
|
registry?: string; // https://kevisual.cn
|
||||||
|
/**
|
||||||
|
* 前端代理,比如/root/home 转到https://kevisual.cn/root/home
|
||||||
|
* path?: string;
|
||||||
|
* target?: string;
|
||||||
|
* pathname?: string;
|
||||||
|
* 例子: { path: '/root/home', target: 'https://kevisual.cn', pathname: '/root/home' }
|
||||||
|
*/
|
||||||
proxy?: ProxyInfo[];
|
proxy?: ProxyInfo[];
|
||||||
apiProxyList?: ProxyInfo[];
|
/**
|
||||||
|
* API 代理配置, 比如,api开头的,v1开头的等等
|
||||||
|
*/
|
||||||
|
api?: {
|
||||||
|
proxy?: ProxyInfo[];
|
||||||
|
}
|
||||||
description?: string;
|
description?: string;
|
||||||
/**
|
/**
|
||||||
* 服务启动
|
* 服务启动,
|
||||||
|
* path是配置 127.0.0.1
|
||||||
|
* port是配置端口号
|
||||||
*/
|
*/
|
||||||
server?: {
|
server?: {
|
||||||
path?: string;
|
path?: string;
|
||||||
port?: number;
|
port?: number;
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* 被远程调用配置
|
||||||
|
* url: 远程应用地址 https://kevisual.cn/ws/proxy
|
||||||
|
* enabled: 是否启用远程应用
|
||||||
|
*/
|
||||||
share?: {
|
share?: {
|
||||||
url: string;
|
url: string;
|
||||||
enabled?: boolean; // 是否启用远程应用
|
enabled?: boolean;
|
||||||
name: string;
|
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* 对pages目录文件监听
|
||||||
|
*/
|
||||||
watch?: {
|
watch?: {
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* 首页
|
* 首页, 访问 `/` 自动会打开的首页地址
|
||||||
|
* 例如: /root/home
|
||||||
*/
|
*/
|
||||||
home?: string;
|
home?: string;
|
||||||
|
/**
|
||||||
|
* 启用本地AI代理
|
||||||
|
*/
|
||||||
ai?: {
|
ai?: {
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
provider?: string | 'DeepSeek' | 'SiliconFlow';
|
provider?: string | 'DeepSeek' | 'Custom';
|
||||||
apiKey?: string;
|
apiKey?: string;
|
||||||
model?: string;
|
model?: string;
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* 自定义脚本, asst 启动时会执行这些脚本
|
||||||
|
*/
|
||||||
scripts?: {
|
scripts?: {
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* 认证和权限配置
|
||||||
|
* share: protected 需要认证代理访问(默认), public 公开访问, private 私有访问
|
||||||
|
* share 是对外共享 pages 目录下的页面
|
||||||
|
*/
|
||||||
auth?: AuthPermission;
|
auth?: AuthPermission;
|
||||||
|
/**
|
||||||
|
* HTTPS 证书配置, 启用后,助手服务会启用 HTTPS 服务, 默认 HTTP
|
||||||
|
* 理论上也不需要https,因为可以通过反向代理实现https
|
||||||
|
*/
|
||||||
https?: {
|
https?: {
|
||||||
type?: 'https' | 'http';
|
type?: 'https' | 'http';
|
||||||
keyPath?: string; // 证书私钥路径
|
keyPath?: string; // 证书私钥路径
|
||||||
@@ -149,12 +195,14 @@ export class AssistantConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
getConfigPath() { }
|
getConfigPath() { }
|
||||||
getConfig() {
|
getConfig(): AssistantConfigData {
|
||||||
try {
|
try {
|
||||||
if (!checkFileExists(this.configPath.configPath)) {
|
if (!checkFileExists(this.configPath.configPath)) {
|
||||||
fs.writeFileSync(this.configPath.configPath, JSON.stringify({ proxy: [] }, null, 2));
|
fs.writeFileSync(this.configPath.configPath, JSON.stringify({ proxy: [] }, null, 2));
|
||||||
return {
|
return {
|
||||||
pageApi: '',
|
app: {
|
||||||
|
url: 'https://kevisual.cn',
|
||||||
|
},
|
||||||
proxy: [],
|
proxy: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -163,7 +211,9 @@ export class AssistantConfig {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('file read', error.message);
|
console.error('file read', error.message);
|
||||||
return {
|
return {
|
||||||
pageApi: '',
|
app: {
|
||||||
|
url: 'https://kevisual.cn',
|
||||||
|
},
|
||||||
proxy: [],
|
proxy: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -176,14 +226,19 @@ export class AssistantConfig {
|
|||||||
}
|
}
|
||||||
getRegistry() {
|
getRegistry() {
|
||||||
const config = this.getCacheAssistantConfig();
|
const config = this.getCacheAssistantConfig();
|
||||||
return config?.registry || config?.pageApi;
|
return config?.registry || config?.app?.url || 'https://kevisual.cn';
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 设置 assistant-config.json 配置
|
* 设置 assistant-config.json 配置
|
||||||
* @param config
|
* @param config
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
setConfig(config?: AssistantConfigData) {
|
setConfig(config?: AssistantConfigData, force?: boolean) {
|
||||||
|
if (force) {
|
||||||
|
this.config = config || {};
|
||||||
|
fs.writeFileSync(this.configPath.configPath, JSON.stringify(this.config, null, 2));
|
||||||
|
return this.config;
|
||||||
|
}
|
||||||
const myConfig = this.getCacheAssistantConfig();
|
const myConfig = this.getCacheAssistantConfig();
|
||||||
const newConfig = { ...myConfig, ...config };
|
const newConfig = { ...myConfig, ...config };
|
||||||
this.config = newConfig;
|
this.config = newConfig;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export type ProxyInfo = {
|
|||||||
*/
|
*/
|
||||||
path?: string;
|
path?: string;
|
||||||
/**
|
/**
|
||||||
* 目标地址
|
* 目标url地址,比如http://localhost:3000
|
||||||
*/
|
*/
|
||||||
target?: string;
|
target?: string;
|
||||||
/**
|
/**
|
||||||
@@ -23,10 +23,12 @@ export type ProxyInfo = {
|
|||||||
*/
|
*/
|
||||||
ws?: boolean;
|
ws?: boolean;
|
||||||
/**
|
/**
|
||||||
* 首要文件,比如index.html, type为fileProxy代理有用 设置了首要文件,如果文件不存在,则访问首要文件
|
* type为file时有效
|
||||||
|
* 索引文件,比如index.html, type为fileProxy代理有用 设置了索引文件,如果文件不存在,则访问索引文件
|
||||||
*/
|
*/
|
||||||
indexPath?: string;
|
indexPath?: string;
|
||||||
/**
|
/**
|
||||||
|
* type为file时有效
|
||||||
* 根路径, 默认是process.cwd(), type为fileProxy代理有用,必须为绝对路径
|
* 根路径, 默认是process.cwd(), type为fileProxy代理有用,必须为绝对路径
|
||||||
*/
|
*/
|
||||||
rootPath?: string;
|
rootPath?: string;
|
||||||
|
|||||||
42
assistant/src/module/http-token.ts
Normal file
42
assistant/src/module/http-token.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import http from 'http';
|
||||||
|
export const error = (msg: string, code = 500) => {
|
||||||
|
return JSON.stringify({ code, message: msg });
|
||||||
|
};
|
||||||
|
const cookie = {
|
||||||
|
parse: (cookieStr: string) => {
|
||||||
|
const cookies: Record<string, string> = {};
|
||||||
|
const cookiePairs = cookieStr.split(';');
|
||||||
|
for (const pair of cookiePairs) {
|
||||||
|
const [key, value] = pair.split('=').map((v) => v.trim());
|
||||||
|
if (key && value) {
|
||||||
|
cookies[key] = decodeURIComponent(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cookies;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const getToken = async (req: http.IncomingMessage, res: http.ServerResponse) => {
|
||||||
|
let token = (req.headers?.['authorization'] as string) || (req.headers?.['Authorization'] as string) || '';
|
||||||
|
const url = new URL(req.url || '', 'http://localhost');
|
||||||
|
const resNoPermission = () => {
|
||||||
|
res.statusCode = 401;
|
||||||
|
res.end(error('Invalid authorization'));
|
||||||
|
return { tokenUser: null, token: null };
|
||||||
|
};
|
||||||
|
if (!token) {
|
||||||
|
token = url.searchParams.get('token') || '';
|
||||||
|
}
|
||||||
|
if (!token) {
|
||||||
|
const parsedCookies = cookie.parse(req.headers.cookie || '');
|
||||||
|
token = parsedCookies.token || '';
|
||||||
|
}
|
||||||
|
if (!token) {
|
||||||
|
return resNoPermission();
|
||||||
|
}
|
||||||
|
if (token) {
|
||||||
|
token = token.replace('Bearer ', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
return { token };
|
||||||
|
};
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ export class LocalProxy {
|
|||||||
initFromAssistantConfig(assistantConfig?: AssistantConfig) {
|
initFromAssistantConfig(assistantConfig?: AssistantConfig) {
|
||||||
if (!assistantConfig) return;
|
if (!assistantConfig) return;
|
||||||
this.pagesDir = assistantConfig.configPath?.pagesDir || '';
|
this.pagesDir = assistantConfig.configPath?.pagesDir || '';
|
||||||
this.watch = !!assistantConfig.getCacheAssistantConfig()?.watch.enabled;
|
this.watch = assistantConfig.getCacheAssistantConfig?.()?.watch?.enabled ?? true;
|
||||||
this.init();
|
this.init();
|
||||||
if (this.watch) {
|
if (this.watch) {
|
||||||
this.onWatch();
|
this.onWatch();
|
||||||
@@ -112,14 +112,26 @@ export class LocalProxy {
|
|||||||
};
|
};
|
||||||
fs.watch(frontAppDir, { recursive: true }, (eventType, filename) => {
|
fs.watch(frontAppDir, { recursive: true }, (eventType, filename) => {
|
||||||
if (eventType === 'rename' || eventType === 'change') {
|
if (eventType === 'rename' || eventType === 'change') {
|
||||||
|
// 过滤 node_modules 目录
|
||||||
|
if (filename && filename.includes('node_modules')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 只监听 js、html、css 文件
|
||||||
|
const validExtensions = ['.js', '.html', '.css', '.json', '.png'];
|
||||||
|
const hasValidExtension = validExtensions.some(ext => filename && filename.endsWith(ext));
|
||||||
|
|
||||||
|
if (!hasValidExtension) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const filePath = path.join(frontAppDir, filename);
|
const filePath = path.join(frontAppDir, filename);
|
||||||
try {
|
try {
|
||||||
const stat = fs.statSync(filePath);
|
const stat = fs.statSync(filePath);
|
||||||
if (stat.isDirectory() || filename.endsWith('.html')) {
|
if (stat.isFile() || stat.isDirectory()) {
|
||||||
// 重新加载
|
// 重新加载
|
||||||
debounce(that.init.bind(that), 5 * 1000);
|
debounce(that.init.bind(that), 5 * 1000);
|
||||||
}
|
}
|
||||||
} catch (error) {}
|
} catch (error) { }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ import pm2 from 'pm2';
|
|||||||
import { logger } from './logger.ts';
|
import { logger } from './logger.ts';
|
||||||
|
|
||||||
export async function reload() {
|
export async function reload() {
|
||||||
|
if (process.env.PM2_HOME === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
pm2.connect((err) => {
|
pm2.connect((err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
|||||||
@@ -21,7 +21,16 @@ app
|
|||||||
})
|
})
|
||||||
.define(async (ctx) => {
|
.define(async (ctx) => {
|
||||||
const { data } = ctx.query;
|
const { data } = ctx.query;
|
||||||
ctx.body = assistantConfig.setConfig(data);
|
ctx.body = assistantConfig.setConfig(data, true);
|
||||||
reload();
|
reload();
|
||||||
})
|
})
|
||||||
.addTo(app);
|
.addTo(app);
|
||||||
|
|
||||||
|
app.route({
|
||||||
|
path: 'config',
|
||||||
|
key: 'getId'
|
||||||
|
}).define(async (ctx) => {
|
||||||
|
const config = assistantConfig.getCacheAssistantConfig();
|
||||||
|
ctx.body = config?.app?.id || null;
|
||||||
|
|
||||||
|
}).addTo(app);
|
||||||
@@ -9,18 +9,28 @@ import './hot-api/key-sender/index.ts';
|
|||||||
|
|
||||||
import os from 'node:os';
|
import os from 'node:os';
|
||||||
import { authCache } from '@/module/cache/auth.ts';
|
import { authCache } from '@/module/cache/auth.ts';
|
||||||
export const getTokenUser = async (ctx: any) => {
|
const getTokenUser = async (token: string) => {
|
||||||
const query = assistantConfig.query
|
const query = assistantConfig.query
|
||||||
const res = await query.post({
|
const res = await query.post({
|
||||||
path: 'user',
|
path: 'user',
|
||||||
key: 'me',
|
key: 'me',
|
||||||
token: ctx.state.token || ctx.query.token,
|
token: token,
|
||||||
});
|
});
|
||||||
if (res.code !== 200) {
|
return res;
|
||||||
return ctx.throw(401, 'not login');
|
}
|
||||||
|
export const getTokenUserCache = async (token: string) => {
|
||||||
|
const tokenUser = await authCache.get(token);
|
||||||
|
if (tokenUser) {
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: tokenUser,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
const tokenUser = res.data || {};
|
const res = await getTokenUser(token);
|
||||||
return tokenUser;
|
if (res.code === 200) {
|
||||||
|
authCache.set(token, res.data);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
const checkAuth = async (ctx: any, isAdmin = false) => {
|
const checkAuth = async (ctx: any, isAdmin = false) => {
|
||||||
const config = assistantConfig.getConfig();
|
const config = assistantConfig.getConfig();
|
||||||
@@ -33,7 +43,12 @@ const checkAuth = async (ctx: any, isAdmin = false) => {
|
|||||||
// 鉴权代理
|
// 鉴权代理
|
||||||
let tokenUser = await authCache.get(token);
|
let tokenUser = await authCache.get(token);
|
||||||
if (!tokenUser) {
|
if (!tokenUser) {
|
||||||
tokenUser = await getTokenUser(ctx);
|
const tokenUserRes = await getTokenUser(token);
|
||||||
|
if (tokenUserRes.code !== 200) {
|
||||||
|
return ctx.throw(tokenUserRes.code, 'not login');
|
||||||
|
} else {
|
||||||
|
tokenUser = tokenUserRes.data;
|
||||||
|
}
|
||||||
authCache.set(token, tokenUser);
|
authCache.set(token, tokenUser);
|
||||||
}
|
}
|
||||||
ctx.state = {
|
ctx.state = {
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ app.route({
|
|||||||
if (!auth.username) {
|
if (!auth.username) {
|
||||||
// 初始管理员账号
|
// 初始管理员账号
|
||||||
auth.username = loginUser;
|
auth.username = loginUser;
|
||||||
if (!auth.type) {
|
if (!auth.share) {
|
||||||
auth.type = 'public';
|
auth.share = 'protected';
|
||||||
}
|
}
|
||||||
assistantConfig.setConfig({ auth });
|
assistantConfig.setConfig({ auth });
|
||||||
console.log('set first admin user', { username: loginUser });
|
console.log('set first admin user', { username: loginUser });
|
||||||
|
|||||||
@@ -51,11 +51,11 @@ export class AssistantInit extends AssistantConfig {
|
|||||||
return this.#query;
|
return this.#query;
|
||||||
}
|
}
|
||||||
get baseURL() {
|
get baseURL() {
|
||||||
return `${this.getConfig()?.pageApi || 'https://kevisual.cn'}/api/router`;
|
return `${this.getConfig()?.app?.url || 'https://kevisual.cn'}/api/router`;
|
||||||
}
|
}
|
||||||
setQuery(query?: Query) {
|
setQuery(query?: Query) {
|
||||||
this.#query = query || new Query({
|
this.#query = query || new Query({
|
||||||
url: `${this.getConfig()?.pageApi || 'https://kevisual.cn'}/api/router`,
|
url: `${this.getConfig()?.app?.url || 'https://kevisual.cn'}/api/router`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
checkConfigPath() {
|
checkConfigPath() {
|
||||||
@@ -92,6 +92,16 @@ export class AssistantInit extends AssistantConfig {
|
|||||||
if (!checkFileExists(assistantPath, true)) {
|
if (!checkFileExists(assistantPath, true)) {
|
||||||
this.setConfig(this.getDefaultInitAssistantConfig());
|
this.setConfig(this.getDefaultInitAssistantConfig());
|
||||||
console.log(chalk.green('助手配置文件assistant-config.json创建成功'));
|
console.log(chalk.green('助手配置文件assistant-config.json创建成功'));
|
||||||
|
} else {
|
||||||
|
const config = this.getConfig();
|
||||||
|
if (!config?.app?.id) {
|
||||||
|
if (!config.app) {
|
||||||
|
config.app = {};
|
||||||
|
}
|
||||||
|
config.app.id = randomId();
|
||||||
|
this.setConfig(config);
|
||||||
|
console.log(chalk.green('助手配置文件assistant-config.json更新成功'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
initPnpm() {
|
initPnpm() {
|
||||||
@@ -120,7 +130,7 @@ export class AssistantInit extends AssistantConfig {
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "pm2 start apps/root/code-center/app.mjs --name root/code-center",
|
"start": "pm2 start apps/root/code-center/app.mjs --name root/code-center",
|
||||||
"proxy": "pm2 start apps/root/page-proxy/app.mjs --name root/page-proxy"
|
"cnb": "ASSISTANT_CONFIG_DIR=/workspace asst server -s -p 7878"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
@@ -194,20 +204,18 @@ export class AssistantInit extends AssistantConfig {
|
|||||||
protected getDefaultInitAssistantConfig() {
|
protected getDefaultInitAssistantConfig() {
|
||||||
const id = randomId();
|
const id = randomId();
|
||||||
return {
|
return {
|
||||||
id,
|
app: {
|
||||||
|
url: 'https://kevisual.cn',
|
||||||
|
id,
|
||||||
|
},
|
||||||
description: '助手配置文件',
|
description: '助手配置文件',
|
||||||
docs: "https://kevisual.cn/root/cli-docs/",
|
docs: "https://kevisual.cn/root/cli/docs/",
|
||||||
home: '/root/home',
|
home: '/root/home',
|
||||||
proxy: [],
|
proxy: [],
|
||||||
apiProxyList: [],
|
|
||||||
share: {
|
share: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
name: 'abc',
|
|
||||||
url: 'https://kevisual.cn/ws/proxy',
|
url: 'https://kevisual.cn/ws/proxy',
|
||||||
},
|
},
|
||||||
watch: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
} as AssistantConfigData;
|
} as AssistantConfigData;
|
||||||
}
|
}
|
||||||
getHttps() {
|
getHttps() {
|
||||||
|
|||||||
@@ -3,14 +3,72 @@ import http from 'node:http';
|
|||||||
import { LocalProxy } from './local-proxy.ts';
|
import { LocalProxy } from './local-proxy.ts';
|
||||||
import { assistantConfig, app } from '@/app.ts';
|
import { assistantConfig, app } from '@/app.ts';
|
||||||
import { log, logger } from '@/module/logger.ts';
|
import { log, logger } from '@/module/logger.ts';
|
||||||
|
import { getToken } from '@/module/http-token.ts';
|
||||||
|
import { getTokenUserCache } from '@/routes/index.ts';
|
||||||
const localProxy = new LocalProxy({});
|
const localProxy = new LocalProxy({});
|
||||||
localProxy.initFromAssistantConfig(assistantConfig);
|
localProxy.initFromAssistantConfig(assistantConfig);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 过滤访问的资源,允许谁访问
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const authFilter = async (req: http.IncomingMessage, res: http.ServerResponse) => {
|
||||||
|
const _assistantConfig = assistantConfig.getCacheAssistantConfig();
|
||||||
|
const auth = _assistantConfig?.auth || {};
|
||||||
|
const share = auth.share || 'protected';
|
||||||
|
const noAdmin = !auth.username;
|
||||||
|
if (noAdmin) return false;
|
||||||
|
const admin = auth.username;
|
||||||
|
const admins = auth.admin || [];
|
||||||
|
if (admin) {
|
||||||
|
admins.push(admin);
|
||||||
|
}
|
||||||
|
const url = new URL(req.url, 'http://localhost');
|
||||||
|
const pathname = decodeURIComponent(url.pathname);
|
||||||
|
// 放开 /
|
||||||
|
if (pathname === '/' || pathname === '/favicon.ico') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 放开首页
|
||||||
|
if (pathname.startsWith('/root/home')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 放开api, 以 /api, /v1, /client, /serve 开头的请求
|
||||||
|
const openApiPaths = ['/api', '/v1', '/client', '/serve'];
|
||||||
|
for (const openPath of openApiPaths) {
|
||||||
|
if (pathname.startsWith(openPath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (share === 'public') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const { token } = await getToken(req, res)
|
||||||
|
if (!token) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const tokenUser = await getTokenUserCache(token);
|
||||||
|
if (share === 'protected' && tokenUser?.code === 200) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (share === 'private') {
|
||||||
|
if (tokenUser?.code === 200) {
|
||||||
|
const username = tokenUser?.data?.username;
|
||||||
|
if (admins.includes(username)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
export const proxyRoute = async (req: http.IncomingMessage, res: http.ServerResponse) => {
|
export const proxyRoute = async (req: http.IncomingMessage, res: http.ServerResponse) => {
|
||||||
const _assistantConfig = assistantConfig.getCacheAssistantConfig();
|
const _assistantConfig = assistantConfig.getCacheAssistantConfig();
|
||||||
const home = _assistantConfig?.home || '/root/home';
|
const home = _assistantConfig?.home || '/root/home';
|
||||||
const auth = _assistantConfig?.auth || {};
|
const auth = _assistantConfig?.auth || {};
|
||||||
let noAdmin = !auth.username;
|
let noAdmin = !auth.username;
|
||||||
|
|
||||||
const toSetting = () => {
|
const toSetting = () => {
|
||||||
res.writeHead(302, { Location: `/root/cli/setting/` });
|
res.writeHead(302, { Location: `/root/cli/setting/` });
|
||||||
res.end();
|
res.end();
|
||||||
@@ -35,9 +93,9 @@ export const proxyRoute = async (req: http.IncomingMessage, res: http.ServerResp
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// client, api, v1, serve 开头的拦截
|
// client, api, v1, serve 开头的拦截
|
||||||
const apiProxyList = _assistantConfig?.apiProxyList || [];
|
const apiProxy = _assistantConfig?.api?.proxy || [];
|
||||||
const defaultApiProxy = createApiProxy(_assistantConfig?.pageApi || 'https://kevisual.cn');
|
const defaultApiProxy = createApiProxy(_assistantConfig?.app?.url || 'https://kevisual.cn');
|
||||||
const apiBackendProxy = [...apiProxyList, ...defaultApiProxy].find((item) => pathname.startsWith(item.path));
|
const apiBackendProxy = [...apiProxy, ...defaultApiProxy].find((item) => pathname.startsWith(item.path));
|
||||||
if (apiBackendProxy) {
|
if (apiBackendProxy) {
|
||||||
log.debug('apiBackendProxy', { apiBackendProxy, url: req.url });
|
log.debug('apiBackendProxy', { apiBackendProxy, url: req.url });
|
||||||
return httpProxy(req, res, {
|
return httpProxy(req, res, {
|
||||||
@@ -76,6 +134,17 @@ export const proxyRoute = async (req: http.IncomingMessage, res: http.ServerResp
|
|||||||
...proxyApi,
|
...proxyApi,
|
||||||
indexPath: _indexPath, // 首页路径
|
indexPath: _indexPath, // 首页路径
|
||||||
});
|
});
|
||||||
|
} else if (proxyApi && proxyApi.type === 'http') {
|
||||||
|
log.debug('proxyApi http', { proxyApi, pathname });
|
||||||
|
return httpProxy(req, res, {
|
||||||
|
path: proxyApi.path,
|
||||||
|
target: proxyApi.target,
|
||||||
|
type: 'http',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const filter = await authFilter(req, res);
|
||||||
|
if (filter) {
|
||||||
|
return res.end('Not Authorized Proxy');
|
||||||
}
|
}
|
||||||
const localProxyProxyList = localProxy.getLocalProxyList();
|
const localProxyProxyList = localProxy.getLocalProxyList();
|
||||||
const localProxyProxy = localProxyProxyList.find((item) => pathname.startsWith(item.path));
|
const localProxyProxy = localProxyProxyList.find((item) => pathname.startsWith(item.path));
|
||||||
@@ -87,7 +156,7 @@ export const proxyRoute = async (req: http.IncomingMessage, res: http.ServerResp
|
|||||||
indexPath: localProxyProxy.indexPath,
|
indexPath: localProxyProxy.indexPath,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const creatCenterProxy = createApiProxy(_assistantConfig?.pageApi || 'https://kevisual.cn', ['/root', '/' + _user]);
|
const creatCenterProxy = createApiProxy(_assistantConfig?.app?.url || 'https://kevisual.cn', ['/root', '/' + _user]);
|
||||||
const centerProxy = creatCenterProxy.find((item) => pathname.startsWith(item.path));
|
const centerProxy = creatCenterProxy.find((item) => pathname.startsWith(item.path));
|
||||||
if (centerProxy) {
|
if (centerProxy) {
|
||||||
return httpProxy(req, res, {
|
return httpProxy(req, res, {
|
||||||
@@ -103,9 +172,9 @@ export const proxyRoute = async (req: http.IncomingMessage, res: http.ServerResp
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const proxyWs = () => {
|
export const proxyWs = () => {
|
||||||
const apiProxyList = assistantConfig.getCacheAssistantConfig()?.apiProxyList || [];
|
const apiProxy = assistantConfig.getCacheAssistantConfig()?.api?.proxy || [];
|
||||||
const proxy = assistantConfig.getCacheAssistantConfig()?.proxy || [];
|
const proxy = assistantConfig.getCacheAssistantConfig()?.proxy || [];
|
||||||
const proxyApi = [...apiProxyList, ...proxy].filter((item) => item.ws);
|
const proxyApi = [...apiProxy, ...proxy].filter((item) => item.ws);
|
||||||
log.debug('proxyApi ', proxyApi);
|
log.debug('proxyApi ', proxyApi);
|
||||||
wsProxy(app.server.server, {
|
wsProxy(app.server.server, {
|
||||||
apiList: proxyApi,
|
apiList: proxyApi,
|
||||||
|
|||||||
Reference in New Issue
Block a user