mirror of
https://github.com/certd/certd.git
synced 2026-04-23 11:37:23 +08:00
feat: 【破坏性更新】插件改为metadata加载模式,plugin-cert、plugin-lib包部分代码转移到certd-server中,影响自定义插件,需要修改相关import引用
ssh、aliyun、tencent、qiniu、oss等 access和client需要转移import
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline";
|
||||
import { HttpClient } from "@certd/basic";
|
||||
import { OnePanelClient } from "./client.js";
|
||||
|
||||
/**
|
||||
* 这个注解将注册一个授权配置
|
||||
* 在certd的后台管理系统中,用户可以选择添加此类型的授权
|
||||
*/
|
||||
@IsAccess({
|
||||
name: "1panel",
|
||||
title: "1panel授权",
|
||||
desc: "账号和密码",
|
||||
icon: "svg:icon-onepanel",
|
||||
})
|
||||
export class OnePanelAccess extends BaseAccess {
|
||||
@AccessInput({
|
||||
title: "1Panel面板的url",
|
||||
component: {
|
||||
placeholder: "http://xxxx.com:1231",
|
||||
},
|
||||
helper: "不要带安全入口",
|
||||
required: true,
|
||||
})
|
||||
baseUrl = "";
|
||||
|
||||
@AccessInput({
|
||||
title: "安全入口",
|
||||
component: {
|
||||
placeholder: "登录的安全入口",
|
||||
},
|
||||
encrypt: true,
|
||||
required: false,
|
||||
})
|
||||
safeEnter = "";
|
||||
|
||||
@AccessInput({
|
||||
title: "授权方式",
|
||||
component: {
|
||||
name: "a-select",
|
||||
vModel: "value",
|
||||
options: [
|
||||
{ label: "模拟登录【不推荐】", value: "password" },
|
||||
{ label: "接口密钥【推荐】", value: "apikey" },
|
||||
],
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
type = "";
|
||||
|
||||
@AccessInput({
|
||||
title: "接口版本",
|
||||
value: "v1",
|
||||
component: {
|
||||
placeholder: "v1 / v2",
|
||||
name: "a-select",
|
||||
vModel: "value",
|
||||
options: [
|
||||
{ label: "v1", value: "v1" },
|
||||
{ label: "v2", value: "v2" },
|
||||
],
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
apiVersion = "v1";
|
||||
|
||||
@AccessInput({
|
||||
title: "用户名",
|
||||
component: {
|
||||
placeholder: "username",
|
||||
},
|
||||
mergeScript: `
|
||||
return {
|
||||
show: ctx.compute(({form})=>{
|
||||
return form.access.type === 'password';
|
||||
})
|
||||
}
|
||||
`,
|
||||
required: true,
|
||||
})
|
||||
username = "";
|
||||
|
||||
@AccessInput({
|
||||
title: "密码",
|
||||
component: {
|
||||
placeholder: "password",
|
||||
},
|
||||
helper: "",
|
||||
mergeScript: `
|
||||
return {
|
||||
show: ctx.compute(({form})=>{
|
||||
return form.access.type === 'password';
|
||||
})
|
||||
}
|
||||
`,
|
||||
required: true,
|
||||
encrypt: true,
|
||||
})
|
||||
password = "";
|
||||
|
||||
@AccessInput({
|
||||
title: "接口密钥",
|
||||
component: {
|
||||
placeholder: "接口密钥",
|
||||
},
|
||||
mergeScript: `
|
||||
return {
|
||||
show: ctx.compute(({form})=>{
|
||||
return form.access.type === 'apikey';
|
||||
})
|
||||
}
|
||||
`,
|
||||
helper: "面板设置->API接口中获取",
|
||||
required: true,
|
||||
encrypt: true,
|
||||
})
|
||||
apiKey = "";
|
||||
|
||||
@AccessInput({
|
||||
title: "忽略证书校验",
|
||||
value: true,
|
||||
component: {
|
||||
name: "a-switch",
|
||||
vModel: "checked",
|
||||
},
|
||||
helper: "如果面板的url是https,且使用的是自签名证书,则需要开启此选项,其他情况可以关闭",
|
||||
})
|
||||
skipSslVerify = true;
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "onTestRequest",
|
||||
},
|
||||
helper: "点击测试接口看是否正常\nIP需要加白名单,如果是同一台机器部署的,可以试试面板的url使用网卡docker0的ip,白名单使用172.16.0.0/12",
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
async onTestRequest() {
|
||||
const http: HttpClient = this.ctx.http;
|
||||
const client = new OnePanelClient({
|
||||
logger: this.ctx.logger,
|
||||
http,
|
||||
access: this,
|
||||
utils: this.ctx.utils,
|
||||
});
|
||||
|
||||
await client.doRequest({
|
||||
url: `/api/${this.apiVersion}/websites/ssl/search`,
|
||||
method: "post",
|
||||
data: {
|
||||
page: 1,
|
||||
pageSize: 1,
|
||||
},
|
||||
});
|
||||
return "ok";
|
||||
}
|
||||
}
|
||||
|
||||
new OnePanelAccess();
|
||||
@@ -0,0 +1,228 @@
|
||||
import { HttpClient, HttpRequestConfig, ILogger } from "@certd/basic";
|
||||
import { OnePanelAccess } from "./access.js";
|
||||
|
||||
export class OnePanelClient {
|
||||
access: OnePanelAccess;
|
||||
http: HttpClient;
|
||||
logger: ILogger;
|
||||
utils: any;
|
||||
token: string;
|
||||
constructor(opts: { access: OnePanelAccess; http: HttpClient; logger: ILogger; utils: any }) {
|
||||
this.access = opts.access;
|
||||
this.http = opts.http;
|
||||
this.logger = opts.logger;
|
||||
this.utils = opts.utils;
|
||||
}
|
||||
|
||||
//
|
||||
// //http://xxx:xxxx/1panel/swagger/index.html#/App/get_apps__key
|
||||
// async execute(): Promise<void> {
|
||||
// //login 获取token
|
||||
// /**
|
||||
// * curl 'http://127.0.0.1:7001/api/v1/auth/login' --data-binary '{"name":"admin_test","password":"admin_test1234","ignoreCaptcha":true,"captcha":"","captchaID":"nY8Cqeut3TjZMfJMAz0k","authMethod":"jwt","language":"zh"}' -H 'EntranceCode: emhhbmd5eg=='
|
||||
// * curl 'http://127.0.0.1:7001/api/v1/dashboard/current/all/all' -H 'PanelAuthorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJJRCI6MCwiTmFtZSI6ImFkbWluX3Rlc3QiLCJCdWZmZXJUaW1lIjozNjAwLCJpc3MiOiIxUGFuZWwiLCJleHAiOjE3MDkyODg4MDl9.pdknJdjLY4Fp8wCE9Gvaiic2rLoSdvUSJB9ossyya_I'
|
||||
// */
|
||||
// const sslIds = this.sslIds;
|
||||
// for (const sslId of sslIds) {
|
||||
// try {
|
||||
// const certRes = await this.get1PanelCertInfo(sslId);
|
||||
// if (!this.isNeedUpdate(certRes)) {
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// const uploadRes = await this.doRequest({
|
||||
// url: "/api/v1/websites/ssl/upload",
|
||||
// method: "post",
|
||||
// data: {
|
||||
// sslIds,
|
||||
// certificate: this.cert.crt,
|
||||
// certificatePath: "",
|
||||
// description: certRes.description || this.appendTimeSuffix("certd"),
|
||||
// privateKey: this.cert.key,
|
||||
// privateKeyPath: "",
|
||||
// sslID: sslId,
|
||||
// type: "paste",
|
||||
// },
|
||||
// });
|
||||
// console.log("uploadRes", JSON.stringify(uploadRes));
|
||||
// } catch (e) {
|
||||
// this.logger.warn(`更新证书(id:${sslId})失败`, e);
|
||||
// this.logger.info("可能1Panel正在重启,等待10秒后检查证书是否更新成功");
|
||||
// await this.ctx.utils.sleep(10000);
|
||||
// const certRes = await this.get1PanelCertInfo(sslId);
|
||||
// if (!this.isNeedUpdate(certRes)) {
|
||||
// continue;
|
||||
// }
|
||||
// throw e;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
async get1PanelCertInfo(sslId: string) {
|
||||
const certRes = await this.doRequest({
|
||||
url: `/api/${this.access.apiVersion}/websites/ssl/${sslId}`,
|
||||
method: "get",
|
||||
});
|
||||
if (!certRes) {
|
||||
throw new Error(`没有找到证书(id:${sslId}),请先在1Panel中手动上传证书,后续才可以自动更新`);
|
||||
}
|
||||
return certRes;
|
||||
}
|
||||
|
||||
async doRequest(config: { currentNode?: string } & HttpRequestConfig<any>) {
|
||||
const tokenHeaders = await this.getAccessToken();
|
||||
config.headers = {
|
||||
...tokenHeaders,
|
||||
};
|
||||
if (config.currentNode) {
|
||||
config.headers.CurrentNode = this.getNodeValue(config.currentNode);
|
||||
delete config.currentNode;
|
||||
}
|
||||
return await this.doRequestWithoutAuth(config);
|
||||
}
|
||||
|
||||
async doRequestWithoutAuth(config: HttpRequestConfig<any>) {
|
||||
config.baseURL = this.access.baseUrl;
|
||||
config.skipSslVerify = this.access.skipSslVerify ?? false;
|
||||
config.logRes = false;
|
||||
config.logParams = false;
|
||||
const res = await this.http.request(config);
|
||||
if (config.returnOriginRes) {
|
||||
return res;
|
||||
}
|
||||
if (res.code === 200) {
|
||||
return res.data;
|
||||
}
|
||||
throw new Error(res.message);
|
||||
}
|
||||
|
||||
async getCookie(name: string) {
|
||||
// https://www.docmirror.cn:20001/api/v1/auth/language
|
||||
const response = await this.doRequestWithoutAuth({
|
||||
url: `/api/${this.access.apiVersion}/auth/language`,
|
||||
method: "GET",
|
||||
returnOriginRes: true,
|
||||
});
|
||||
const cookies = response.headers["set-cookie"];
|
||||
//根据name 返回对应的cookie
|
||||
const found = cookies.find(cookie => cookie.includes(name));
|
||||
if (!found) {
|
||||
return null;
|
||||
}
|
||||
const cookie = found.split(";")[0];
|
||||
return cookie.substring(cookie.indexOf("=") + 1);
|
||||
}
|
||||
|
||||
async encryptPassword(password: string) {
|
||||
const rsaPublicKeyText = await this.getCookie("panel_public_key");
|
||||
if (!rsaPublicKeyText) {
|
||||
return password;
|
||||
}
|
||||
// 使用rsa加密
|
||||
const { encryptPassword } = await import("./util.js");
|
||||
return encryptPassword(rsaPublicKeyText, password);
|
||||
}
|
||||
|
||||
async getAccessToken() {
|
||||
if (this.access.type === "apikey") {
|
||||
return this.getAccessTokenByApiKey();
|
||||
} else {
|
||||
return await this.getAccessTokenByPassword();
|
||||
}
|
||||
}
|
||||
|
||||
async getAccessTokenByApiKey() {
|
||||
/**
|
||||
* Token = md5('1panel' + API-Key + UnixTimestamp)
|
||||
* 组成部分:
|
||||
* 固定前缀: '1panel'
|
||||
* API-Key: 面板 API 接口密钥
|
||||
* UnixTimestamp: 当前的时间戳(秒级)
|
||||
* 请求 Header 设计¶
|
||||
* 每次请求必须携带以下两个 Header:
|
||||
*
|
||||
* Header 名称 说明
|
||||
* 1Panel-Token 自定义的 Token 值
|
||||
* 1Panel-Timestamp 当前时间戳
|
||||
* 示例请求头:¶
|
||||
*
|
||||
* curl -X GET "http://localhost:4004/api/v1/dashboard/current" \
|
||||
* -H "1Panel-Token: <1panel_token>" \
|
||||
* -H "1Panel-Timestamp: <current_unix_timestamp>"
|
||||
*/
|
||||
|
||||
const timestamp = Math.floor(Date.now() / 1000);
|
||||
const token = this.utils.hash.md5(`1panel${this.access.apiKey}${timestamp}`);
|
||||
return {
|
||||
"1Panel-Token": token,
|
||||
"1Panel-Timestamp": timestamp,
|
||||
};
|
||||
}
|
||||
|
||||
async getAccessTokenByPassword() {
|
||||
// console.log("getAccessToken", this);
|
||||
// const tokenCacheKey = `1panel-token-${this.accessId}`;
|
||||
// let token = this.utils.cache.get(tokenCacheKey);
|
||||
// if (token) {
|
||||
// return token;
|
||||
// }
|
||||
if (this.token) {
|
||||
return {
|
||||
PanelAuthorization: this.token,
|
||||
};
|
||||
}
|
||||
let password = this.access.password;
|
||||
password = await this.encryptPassword(password);
|
||||
const loginRes = await this.doRequestWithoutAuth({
|
||||
url: `/api/${this.access.apiVersion}/auth/login`,
|
||||
method: "post",
|
||||
headers: {
|
||||
EntranceCode: Buffer.from(this.access.safeEnter).toString("base64"),
|
||||
},
|
||||
data: {
|
||||
name: this.access.username,
|
||||
password: password,
|
||||
ignoreCaptcha: true,
|
||||
captcha: "",
|
||||
captchaID: "",
|
||||
authMethod: "jwt",
|
||||
language: "zh",
|
||||
},
|
||||
});
|
||||
this.token = loginRes.token;
|
||||
|
||||
return {
|
||||
PanelAuthorization: this.token,
|
||||
};
|
||||
}
|
||||
|
||||
async onGetSSLIds() {
|
||||
// if (!isPlus()) {
|
||||
// throw new Error("自动获取站点列表为专业版功能,您可以手动输入证书id进行部署");
|
||||
// }
|
||||
const res = await this.doRequest({
|
||||
url: `/api/${this.access.apiVersion}/websites/ssl/search`,
|
||||
method: "post",
|
||||
data: {
|
||||
page: 1,
|
||||
pageSize: 99999,
|
||||
},
|
||||
});
|
||||
if (!res?.items) {
|
||||
throw new Error("没有找到证书,请先在1Panel中手动上传证书,并关联站点,后续才可以自动更新");
|
||||
}
|
||||
const options = res.items.map(item => {
|
||||
return {
|
||||
label: `${item.primaryDomain}<${item.id},${item.description || "无备注"}>`,
|
||||
value: item.id,
|
||||
};
|
||||
});
|
||||
return options;
|
||||
}
|
||||
|
||||
getNodeValue(node?: string) {
|
||||
const node_master_key = "local";
|
||||
const _value = node || node_master_key;
|
||||
return encodeURIComponent(_value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./plugins/index.js";
|
||||
export * from "./access.js";
|
||||
export * from "./client.js";
|
||||
@@ -0,0 +1,212 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
|
||||
|
||||
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
|
||||
import { OnePanelAccess } from "../access.js";
|
||||
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
|
||||
import { OnePanelClient } from "../client.js";
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: "1PanelDeployToWebsitePlugin",
|
||||
title: "1Panel-部署证书到1Panel",
|
||||
icon: "svg:icon-onepanel",
|
||||
desc: "更新1Panel的证书",
|
||||
group: pluginGroups.panel.key,
|
||||
default: {
|
||||
strategy: {
|
||||
runStrategy: RunStrategy.SkipWhenSucceed,
|
||||
},
|
||||
},
|
||||
needPlus: false,
|
||||
})
|
||||
export class OnePanelDeployToWebsitePlugin extends AbstractTaskPlugin {
|
||||
//证书选择,此项必须要有
|
||||
@TaskInput({
|
||||
title: "域名证书",
|
||||
helper: "请选择前置任务输出的域名证书",
|
||||
component: {
|
||||
name: "output-selector",
|
||||
from: [...CertApplyPluginNames],
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
cert!: CertInfo;
|
||||
|
||||
@TaskInput(createCertDomainGetterInputDefine())
|
||||
certDomains!: string[];
|
||||
|
||||
//授权选择框
|
||||
@TaskInput({
|
||||
title: "1Panel授权",
|
||||
helper: "1Panel授权",
|
||||
component: {
|
||||
name: "access-selector",
|
||||
type: "1panel",
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
accessId!: string;
|
||||
|
||||
@TaskInput(
|
||||
createRemoteSelectInputDefine({
|
||||
title: "1Panel节点",
|
||||
helper: "要更新的1Panel证书的节点信息,目前只有v2存在此概念",
|
||||
typeName: "OnePanelDeployToWebsitePlugin",
|
||||
action: OnePanelDeployToWebsitePlugin.prototype.onGetNodes.name,
|
||||
value: "local",
|
||||
required: true,
|
||||
})
|
||||
)
|
||||
currentNode!: string;
|
||||
|
||||
@TaskInput(
|
||||
createRemoteSelectInputDefine({
|
||||
title: "1Panel证书ID",
|
||||
typeName: "1PanelDeployToWebsitePlugin",
|
||||
action: OnePanelDeployToWebsitePlugin.prototype.onGetSSLIds.name,
|
||||
watches: ["accessId"],
|
||||
helper: "要更新的1Panel证书id,选择授权之后,从下拉框中选择\nIP需要加白名单,如果是同一台机器部署的,可以试试172.16.0.0/12",
|
||||
required: true,
|
||||
})
|
||||
)
|
||||
sslIds!: string[];
|
||||
|
||||
access: OnePanelAccess;
|
||||
async onInstance() {
|
||||
this.access = await this.getAccess(this.accessId);
|
||||
}
|
||||
//http://xxx:xxxx/1panel/swagger/index.html#/App/get_apps__key
|
||||
async execute(): Promise<void> {
|
||||
//login 获取token
|
||||
/**
|
||||
* curl 'http://127.0.0.1:7001/api/v1/auth/login' --data-binary '{"name":"admin_test","password":"admin_test1234","ignoreCaptcha":true,"captcha":"","captchaID":"nY8Cqeut3TjZMfJMAz0k","authMethod":"jwt","language":"zh"}' -H 'EntranceCode: emhhbmd5eg=='
|
||||
* curl 'http://127.0.0.1:7001/api/v1/dashboard/current/all/all' -H 'PanelAuthorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJJRCI6MCwiTmFtZSI6ImFkbWluX3Rlc3QiLCJCdWZmZXJUaW1lIjozNjAwLCJpc3MiOiIxUGFuZWwiLCJleHAiOjE3MDkyODg4MDl9.pdknJdjLY4Fp8wCE9Gvaiic2rLoSdvUSJB9ossyya_I'
|
||||
*/
|
||||
|
||||
const client = new OnePanelClient({
|
||||
access: this.access,
|
||||
http: this.http,
|
||||
logger: this.logger,
|
||||
utils: this.ctx.utils,
|
||||
});
|
||||
|
||||
const sslIds = this.sslIds;
|
||||
for (const sslId of sslIds) {
|
||||
try {
|
||||
const certRes = await this.get1PanelCertInfo(client, sslId);
|
||||
if (!this.isNeedUpdate(certRes)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const uploadRes = await client.doRequest({
|
||||
url: `/api/${this.access.apiVersion}/websites/ssl/upload`,
|
||||
method: "post",
|
||||
data: {
|
||||
sslIds,
|
||||
certificate: this.cert.crt,
|
||||
certificatePath: "",
|
||||
description: certRes.description || this.appendTimeSuffix("certd"),
|
||||
privateKey: this.cert.key,
|
||||
privateKeyPath: "",
|
||||
sslID: sslId,
|
||||
type: "paste",
|
||||
},
|
||||
currentNode: this.currentNode,
|
||||
});
|
||||
console.log("uploadRes", JSON.stringify(uploadRes));
|
||||
} catch (e) {
|
||||
this.logger.warn(`更新证书(id:${sslId})失败`, e);
|
||||
this.logger.info("可能1Panel正在重启,等待10秒后检查证书是否更新成功");
|
||||
await this.ctx.utils.sleep(10000);
|
||||
const certRes = await this.get1PanelCertInfo(client, sslId);
|
||||
if (!this.isNeedUpdate(certRes)) {
|
||||
continue;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isNeedUpdate(certRes: any) {
|
||||
if (certRes.pem === this.cert.crt && certRes.key === this.cert.key) {
|
||||
this.logger.info(`证书(id:${certRes.id})已经是最新的了,不需要更新`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async get1PanelCertInfo(client: OnePanelClient, sslId: string) {
|
||||
const certRes = await client.doRequest({
|
||||
url: `/api/${this.access.apiVersion}/websites/ssl/${sslId}`,
|
||||
method: "get",
|
||||
currentNode: this.currentNode,
|
||||
});
|
||||
if (!certRes) {
|
||||
throw new Error(`没有找到证书(id:${sslId}),请先在1Panel中手动上传证书,后续才可以自动更新`);
|
||||
}
|
||||
return certRes;
|
||||
}
|
||||
|
||||
async onGetNodes() {
|
||||
const options = [{ label: "主节点", value: "local" }];
|
||||
if (this.access.apiVersion === "v1") {
|
||||
return options;
|
||||
}
|
||||
if (!this.access) {
|
||||
throw new Error("请先选择授权");
|
||||
}
|
||||
const client = new OnePanelClient({
|
||||
access: this.access,
|
||||
http: this.http,
|
||||
logger: this.logger,
|
||||
utils: this.ctx.utils,
|
||||
});
|
||||
|
||||
const resp = await client.doRequest({
|
||||
url: `/api/${this.access.apiVersion}/core/nodes/list`,
|
||||
method: "post",
|
||||
data: {},
|
||||
});
|
||||
|
||||
// console.log('resp', resp)
|
||||
return [...options, ...(resp?.map(item => ({ label: `${item.addr}(${item.name})`, value: item.name })) || [])];
|
||||
}
|
||||
|
||||
// requestHandle
|
||||
async onGetSSLIds() {
|
||||
// if (!isPlus()) {
|
||||
// throw new Error("自动获取站点列表为专业版功能,您可以手动输入证书id进行部署");
|
||||
// }
|
||||
if (!this.access) {
|
||||
throw new Error("请先选择授权");
|
||||
}
|
||||
const client = new OnePanelClient({
|
||||
access: this.access,
|
||||
http: this.http,
|
||||
logger: this.logger,
|
||||
utils: this.ctx.utils,
|
||||
});
|
||||
const res = await client.doRequest({
|
||||
url: `api/${this.access.apiVersion}/websites/ssl/search`,
|
||||
method: "post",
|
||||
data: {
|
||||
page: 1,
|
||||
pageSize: 99999,
|
||||
},
|
||||
currentNode: this.currentNode,
|
||||
});
|
||||
if (!res?.items) {
|
||||
throw new Error("没有找到证书,请先在1Panel中手动上传证书,并关联站点,后续才可以自动更新");
|
||||
}
|
||||
const list = res.items.map(item => {
|
||||
const domains = item.domains ? [] : item.domains.split(",");
|
||||
const allDomains = [item.primaryDomain, ...domains];
|
||||
return {
|
||||
label: `${item.primaryDomain}<${item.id},${item.description || "无备注"}>`,
|
||||
value: item.id,
|
||||
domain: allDomains,
|
||||
};
|
||||
});
|
||||
return this.ctx.utils.options.buildGroupOptions(list, this.certDomains);
|
||||
}
|
||||
}
|
||||
new OnePanelDeployToWebsitePlugin();
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./deploy-to-website.js";
|
||||
@@ -0,0 +1,65 @@
|
||||
import CryptoJS from "crypto-js";
|
||||
import crypto from "crypto";
|
||||
function rsaEncrypt(data: string, publicKey: string) {
|
||||
if (!data) {
|
||||
return data;
|
||||
}
|
||||
// const jsEncrypt = new JSEncrypt();
|
||||
// jsEncrypt.setPublicKey(publicKey);
|
||||
// return jsEncrypt.encrypt(data);
|
||||
|
||||
// RSA encryption is not supported in browser
|
||||
//换一种nodejs的实现
|
||||
return crypto
|
||||
.publicEncrypt(
|
||||
{
|
||||
key: publicKey,
|
||||
padding: crypto.constants.RSA_PKCS1_PADDING, // 明确指定填充方式
|
||||
},
|
||||
Buffer.from(data, "utf-8")
|
||||
)
|
||||
.toString("base64");
|
||||
}
|
||||
|
||||
function aesEncrypt(data: string, key: string) {
|
||||
const keyBytes = CryptoJS.enc.Utf8.parse(key);
|
||||
const iv = CryptoJS.lib.WordArray.random(16);
|
||||
const encrypted = CryptoJS.AES.encrypt(data, keyBytes, {
|
||||
iv: iv,
|
||||
mode: CryptoJS.mode.CBC,
|
||||
padding: CryptoJS.pad.Pkcs7,
|
||||
});
|
||||
return iv.toString(CryptoJS.enc.Base64) + ":" + encrypted.toString();
|
||||
}
|
||||
|
||||
function urlDecode(value: string): string {
|
||||
return decodeURIComponent(value.replace(/\+/g, " "));
|
||||
}
|
||||
|
||||
function generateAESKey(): string {
|
||||
const keyLength = 16;
|
||||
const randomBytes = new Uint8Array(keyLength);
|
||||
crypto.getRandomValues(randomBytes);
|
||||
return Array.from(randomBytes)
|
||||
.map((b) => b.toString(16).padStart(2, "0"))
|
||||
.join("");
|
||||
}
|
||||
|
||||
export const encryptPassword = (rsaPublicKeyText: string, password: string) => {
|
||||
if (!password) {
|
||||
return "";
|
||||
}
|
||||
// let rsaPublicKeyText = getCookie("panel_public_key");
|
||||
if (!rsaPublicKeyText) {
|
||||
console.log("RSA public key not found");
|
||||
return password;
|
||||
}
|
||||
rsaPublicKeyText = urlDecode(rsaPublicKeyText);
|
||||
|
||||
const aesKey = generateAESKey();
|
||||
rsaPublicKeyText = rsaPublicKeyText.replaceAll('"', "");
|
||||
const rsaPublicKey = atob(rsaPublicKeyText);
|
||||
const keyCipher = rsaEncrypt(aesKey, rsaPublicKey);
|
||||
const passwordCipher = aesEncrypt(password, aesKey);
|
||||
return `${keyCipher}:${passwordCipher}`;
|
||||
};
|
||||
Reference in New Issue
Block a user