update
This commit is contained in:
@@ -155,15 +155,6 @@ export type AssistantConfigData = {
|
||||
* share 是对外共享 pages 目录下的页面
|
||||
*/
|
||||
auth?: AuthPermission;
|
||||
/**
|
||||
* HTTPS 证书配置, 启用后,助手服务会启用 HTTPS 服务, 默认 HTTP
|
||||
* 理论上也不需要https,因为可以通过反向代理实现https
|
||||
*/
|
||||
https?: {
|
||||
type?: 'https' | 'http';
|
||||
keyPath?: string; // 证书私钥路径
|
||||
certPath?: string; // 证书路径
|
||||
};
|
||||
};
|
||||
let assistantConfig: AssistantConfigData;
|
||||
type AssistantConfigOptions = {
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
import { createCert } from '@kevisual/router/sign';
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
import { AssistantConfig } from '../config/index.ts';
|
||||
import { checkFileExists } from '../file/index.ts';
|
||||
import { chalk } from '@/module/chalk.ts';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
type Attributes = {
|
||||
name: string;
|
||||
value: string;
|
||||
};
|
||||
type AltNames = {
|
||||
type: number;
|
||||
value?: string;
|
||||
ip?: string;
|
||||
};
|
||||
export class HttpsPem {
|
||||
assistantConfig: AssistantConfig;
|
||||
key: string;
|
||||
cert: string;
|
||||
isHttps = false;
|
||||
constructor(assistantConfig: AssistantConfig) {
|
||||
this.assistantConfig = assistantConfig;
|
||||
this.#initKeyCert();
|
||||
}
|
||||
#initKeyCert() {
|
||||
this.assistantConfig.checkMounted();
|
||||
const config = this.assistantConfig.getConfig();
|
||||
if (config.https) {
|
||||
const httpsType = config.https?.type || 'https';
|
||||
if (httpsType !== 'https') {
|
||||
// console.log(chalk.yellow('当前配置文件 https.type 不是 https, 不使用证书'));
|
||||
return;
|
||||
}
|
||||
this.isHttps = true;
|
||||
if (config.https.keyPath) {
|
||||
const keyPath = config.https.keyPath;
|
||||
const certPath = config.https.certPath;
|
||||
if (checkFileExists(keyPath) && checkFileExists(certPath)) {
|
||||
this.key = fs.readFileSync(keyPath, 'utf-8');
|
||||
this.cert = fs.readFileSync(certPath, 'utf-8');
|
||||
console.log(chalk.green('使用配置文件 https.keyPath 和 https.certPath 的证书'), keyPath, certPath);
|
||||
return;
|
||||
} else {
|
||||
console.log(chalk.red('证书路径不存在,请检查配置文件 https.keyPath 和 https.certPath 是否正确'));
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!this.isHttps) return;
|
||||
const { key, cert } = this.getCert();
|
||||
this.key = key;
|
||||
this.cert = cert;
|
||||
}
|
||||
getPemDir() {
|
||||
const configDir = this.assistantConfig.configPath?.configDir || process.cwd();
|
||||
const pemDir = path.join(configDir, 'pem');
|
||||
if (!checkFileExists(pemDir)) {
|
||||
fs.mkdirSync(pemDir, { recursive: true });
|
||||
}
|
||||
return pemDir;
|
||||
}
|
||||
getCert() {
|
||||
if (!this.assistantConfig.init) return;
|
||||
const pemDir = this.getPemDir();
|
||||
const pemPath = {
|
||||
key: path.join(pemDir, 'https-private-key.pem'),
|
||||
cert: path.join(pemDir, 'https-cert.pem'),
|
||||
config: path.join(pemDir, 'https-config.json'),
|
||||
};
|
||||
const writeCreate = (opts: { key: string; cert: string; data: { createTime: number; expireTime: number } }) => {
|
||||
fs.writeFileSync(pemPath.key, opts.key);
|
||||
fs.writeFileSync(pemPath.cert, opts.cert);
|
||||
fs.writeFileSync(pemPath.config, JSON.stringify(opts.data, null, 2));
|
||||
};
|
||||
if (!checkFileExists(pemPath.key) || !checkFileExists(pemPath.cert)) {
|
||||
const { key, cert, data } = this.createCert();
|
||||
writeCreate({ key, cert, data });
|
||||
console.log(chalk.green('证书创建成功,浏览器需要导入当前证书'));
|
||||
return {
|
||||
key,
|
||||
cert,
|
||||
};
|
||||
}
|
||||
|
||||
if (!checkFileExists(pemPath.config)) {
|
||||
const data = this.createExpireData();
|
||||
fs.writeFileSync(pemPath.config, JSON.stringify(data, null, 2));
|
||||
}
|
||||
const key = fs.readFileSync(pemPath.key, 'utf-8');
|
||||
const cert = fs.readFileSync(pemPath.cert, 'utf-8');
|
||||
const config = fs.readFileSync(pemPath.config, 'utf-8');
|
||||
let expireTime = 0;
|
||||
try {
|
||||
const data = JSON.parse(config);
|
||||
expireTime = data.expireTime;
|
||||
if (typeof expireTime !== 'number') {
|
||||
throw new Error('expireTime is not a number');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(chalk.red('证书配置文件损坏,重新生成证书'));
|
||||
}
|
||||
const now = new Date().getTime();
|
||||
if (now > expireTime) {
|
||||
this.removeCert();
|
||||
const { key, cert, data } = this.createCert();
|
||||
writeCreate({ key, cert, data });
|
||||
console.log(chalk.green('证书更新成功, 浏览器需要重新导入当前证书'));
|
||||
return {
|
||||
key,
|
||||
cert,
|
||||
};
|
||||
}
|
||||
return {
|
||||
key,
|
||||
cert,
|
||||
};
|
||||
}
|
||||
createExpireData() {
|
||||
const expireTime = new Date().getTime() + 365 * 24 * 60 * 60 * 1000;
|
||||
const expireDate = dayjs(expireTime).format('YYYY-MM-DD HH:mm:ss');
|
||||
return {
|
||||
description: '手动导入证书到浏览器, https-cert.pem文件, 具体使用教程访问 https://kevisual.cn/root/pem-docs/',
|
||||
createTime: new Date().getTime(),
|
||||
expireDate,
|
||||
expireTime,
|
||||
};
|
||||
}
|
||||
/*
|
||||
* 重新生成证书
|
||||
*/
|
||||
removeCert() {
|
||||
const pemDir = this.getPemDir();
|
||||
const pemPath = {
|
||||
key: path.join(pemDir, 'https-private-key.pem'),
|
||||
cert: path.join(pemDir, 'https-cert.pem'),
|
||||
};
|
||||
const oldPath = {
|
||||
key: path.join(pemDir, 'https-private-key.pem.bak'),
|
||||
cert: path.join(pemDir, 'https-cert.pem.bak'),
|
||||
};
|
||||
if (checkFileExists(pemPath.key)) {
|
||||
fs.renameSync(pemPath.key, oldPath.key);
|
||||
}
|
||||
if (checkFileExists(pemPath.cert)) {
|
||||
fs.renameSync(pemPath.cert, oldPath.cert);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 创建证书
|
||||
* @param attrs 证书属性
|
||||
* @param altNames 证书备用名称
|
||||
*/
|
||||
createCert(attrs?: Attributes[], altNames?: AltNames[]) {
|
||||
const attributes = attrs || [];
|
||||
const altNamesList = altNames || [];
|
||||
const { key, cert } = createCert(
|
||||
[
|
||||
{
|
||||
name: 'commonName',
|
||||
value: 'localhost',
|
||||
},
|
||||
{
|
||||
name: 'organizationName',
|
||||
value: 'kevisual',
|
||||
},
|
||||
...attributes,
|
||||
],
|
||||
altNamesList,
|
||||
);
|
||||
const data = this.createExpireData();
|
||||
return {
|
||||
key,
|
||||
cert,
|
||||
data: {
|
||||
...data,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import * as http from 'http';
|
||||
import * as fs from 'fs';
|
||||
import { isBun } from './utils.ts';
|
||||
import { Readable } from 'stream';
|
||||
|
||||
/**
|
||||
* 文件流管道传输函数
|
||||
* 将指定文件的内容通过流的方式传输给客户端响应
|
||||
@@ -112,4 +111,17 @@ export const pipeProxyReq = async (req: http.IncomingMessage, proxyReq: http.Cli
|
||||
// Node.js标准环境下直接使用pipe进行流传输
|
||||
req.pipe(proxyReq, { end: true });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const pipeBusboy = async (req: http.IncomingMessage, res: http.ServerResponse, busboy: any) => {
|
||||
if (isBun) {
|
||||
// @ts-ignore
|
||||
const bunRequest = req.bun.request;
|
||||
const arrayBuffer = await bunRequest.arrayBuffer();
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
busboy.end(buffer);
|
||||
} else {
|
||||
req.pipe(busboy);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user