From 9fa9772c079cc5e7d08cf369260bfef181d0d805 Mon Sep 17 00:00:00 2001 From: zhengkunwang223 <31820853+zhengkunwang223@users.noreply.github.com> Date: Wed, 5 Jul 2023 16:24:19 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=AF=81=E4=B9=A6=E6=89=8B=E5=8A=A8?= =?UTF-8?q?=E5=AF=BC=E5=85=A5=E5=A2=9E=E5=8A=A0=E9=80=89=E6=8B=A9=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E6=96=87=E4=BB=B6=20(#1543)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/dto/request/website.go | 21 +++--- backend/app/service/website.go | 74 ++++++++++++++----- backend/i18n/lang/en.yaml | 4 + backend/i18n/lang/zh-Hant.yaml | 4 + backend/i18n/lang/zh.yaml | 4 + frontend/src/api/interface/app.ts | 1 - frontend/src/components/file-list/index.vue | 15 +++- frontend/src/lang/modules/en.ts | 11 ++- frontend/src/lang/modules/tw.ts | 13 +++- frontend/src/lang/modules/zh.ts | 13 +++- .../website/config/basic/https/index.vue | 52 +++++++++++-- 11 files changed, 163 insertions(+), 49 deletions(-) diff --git a/backend/app/dto/request/website.go b/backend/app/dto/request/website.go index 4e7fba52a..395d30736 100644 --- a/backend/app/dto/request/website.go +++ b/backend/app/dto/request/website.go @@ -115,15 +115,18 @@ type WebsiteDomainDelete struct { } type WebsiteHTTPSOp struct { - WebsiteID uint `json:"websiteId" validate:"required"` - Enable bool `json:"enable" validate:"required"` - WebsiteSSLID uint `json:"websiteSSLId"` - Type string `json:"type" validate:"oneof=existed auto manual"` - PrivateKey string `json:"privateKey"` - Certificate string `json:"certificate"` - HttpConfig string `json:"HttpConfig" validate:"oneof=HTTPSOnly HTTPAlso HTTPToHTTPS"` - SSLProtocol []string `json:"SSLProtocol"` - Algorithm string `json:"algorithm"` + WebsiteID uint `json:"websiteId" validate:"required"` + Enable bool `json:"enable" validate:"required"` + WebsiteSSLID uint `json:"websiteSSLId"` + Type string `json:"type" validate:"oneof=existed auto manual"` + PrivateKey string `json:"privateKey"` + Certificate string `json:"certificate"` + PrivateKeyPath string `json:"privateKeyPath"` + CertificatePath string `json:"certificatePath"` + ImportType string `json:"importType"` + HttpConfig string `json:"httpConfig" validate:"oneof=HTTPSOnly HTTPAlso HTTPToHTTPS"` + SSLProtocol []string `json:"SSLProtocol"` + Algorithm string `json:"algorithm"` } type WebsiteNginxUpdate struct { diff --git a/backend/app/service/website.go b/backend/app/service/website.go index 1ff1278c1..e4991547c 100644 --- a/backend/app/service/website.go +++ b/backend/app/service/website.go @@ -58,7 +58,7 @@ type IWebsiteService interface { UpdateNginxConfigByScope(req request.NginxConfigUpdate) error GetWebsiteNginxConfig(websiteId uint, configType string) (response.FileInfo, error) GetWebsiteHTTPS(websiteId uint) (response.WebsiteHTTPS, error) - OpWebsiteHTTPS(ctx context.Context, req request.WebsiteHTTPSOp) (response.WebsiteHTTPS, error) + OpWebsiteHTTPS(ctx context.Context, req request.WebsiteHTTPSOp) (*response.WebsiteHTTPS, error) PreInstallCheck(req request.WebsiteInstallCheckReq) ([]response.WebsitePreInstallCheck, error) GetWafConfig(req request.WebsiteWafReq) (response.WebsiteWafConfig, error) UpdateWafConfig(req request.WebsiteWafUpdate) error @@ -619,10 +619,10 @@ func (w WebsiteService) GetWebsiteHTTPS(websiteId uint) (response.WebsiteHTTPS, return res, nil } -func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteHTTPSOp) (response.WebsiteHTTPS, error) { +func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteHTTPSOp) (*response.WebsiteHTTPS, error) { website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID)) if err != nil { - return response.WebsiteHTTPS{}, err + return nil, err } var ( res response.WebsiteHTTPS @@ -635,7 +635,7 @@ func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteH website.Protocol = constant.ProtocolHTTP website.WebsiteSSLID = 0 if err := deleteListenAndServerName(website, []string{"443", "[::]:443"}, []string{}); err != nil { - return response.WebsiteHTTPS{}, err + return nil, err } nginxParams := getNginxParamsFromStaticFile(dto.SSL, nil) nginxParams = append(nginxParams, @@ -656,28 +656,64 @@ func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteH Name: "ssl_ciphers", }, ) - if err := deleteNginxConfig(constant.NginxScopeServer, nginxParams, &website); err != nil { - return response.WebsiteHTTPS{}, err + if err = deleteNginxConfig(constant.NginxScopeServer, nginxParams, &website); err != nil { + return nil, err } - if err := websiteRepo.Save(ctx, &website); err != nil { - return response.WebsiteHTTPS{}, err + if err = websiteRepo.Save(ctx, &website); err != nil { + return nil, err } - return res, nil + return nil, nil } if req.Type == constant.SSLExisted { websiteSSL, err = websiteSSLRepo.GetFirst(commonRepo.WithByID(req.WebsiteSSLID)) if err != nil { - return response.WebsiteHTTPS{}, err + return nil, err } website.WebsiteSSLID = websiteSSL.ID res.SSL = websiteSSL } if req.Type == constant.SSLManual { - certBlock, _ := pem.Decode([]byte(req.Certificate)) + var ( + certificate string + privateKey string + ) + switch req.ImportType { + case "paste": + certificate = req.Certificate + privateKey = req.PrivateKey + case "local": + fileOp := files.NewFileOp() + if !fileOp.Stat(req.PrivateKeyPath) { + return nil, buserr.New("ErrSSLKeyNotFound") + } + if !fileOp.Stat(req.CertificatePath) { + return nil, buserr.New("ErrSSLCertificateNotFound") + } + if content, err := fileOp.GetContent(req.PrivateKeyPath); err != nil { + return nil, err + } else { + privateKey = string(content) + } + if content, err := fileOp.GetContent(req.CertificatePath); err != nil { + return nil, err + } else { + certificate = string(content) + } + } + + privateKeyCertBlock, _ := pem.Decode([]byte(privateKey)) + if privateKeyCertBlock == nil { + return nil, buserr.New("ErrSSLCertificateFormat") + } + + certBlock, _ := pem.Decode([]byte(certificate)) + if certBlock == nil { + return nil, buserr.New("ErrSSLCertificateFormat") + } cert, err := x509.ParseCertificate(certBlock.Bytes) if err != nil { - return response.WebsiteHTTPS{}, err + return nil, err } websiteSSL.ExpireDate = cert.NotAfter websiteSSL.StartDate = cert.NotBefore @@ -691,28 +727,28 @@ func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteH websiteSSL.PrimaryDomain = cert.DNSNames[0] websiteSSL.Domains = strings.Join(cert.DNSNames, ",") } - websiteSSL.Provider = constant.Manual - websiteSSL.PrivateKey = req.PrivateKey - websiteSSL.Pem = req.Certificate + websiteSSL.PrivateKey = privateKey + websiteSSL.Pem = certificate + res.SSL = websiteSSL } website.Protocol = constant.ProtocolHTTPS if err := applySSL(website, websiteSSL, req); err != nil { - return response.WebsiteHTTPS{}, err + return nil, err } website.HttpConfig = req.HttpConfig if websiteSSL.ID == 0 { if err := websiteSSLRepo.Create(ctx, &websiteSSL); err != nil { - return response.WebsiteHTTPS{}, err + return nil, err } website.WebsiteSSLID = websiteSSL.ID } if err := websiteRepo.Save(ctx, &website); err != nil { - return response.WebsiteHTTPS{}, err + return nil, err } - return res, nil + return &res, nil } func (w WebsiteService) PreInstallCheck(req request.WebsiteInstallCheckReq) ([]response.WebsitePreInstallCheck, error) { diff --git a/backend/i18n/lang/en.yaml b/backend/i18n/lang/en.yaml index ea97ca35c..60bb9c6e2 100644 --- a/backend/i18n/lang/en.yaml +++ b/backend/i18n/lang/en.yaml @@ -63,6 +63,10 @@ ErrSSLCannotDelete: "The certificate is being used by the website and cannot be ErrAccountCannotDelete: "The certificate associated with the account cannot be deleted" ErrSSLApply: "The certificate continues to be signed successfully, but openresty reload fails, please check the configuration!" ErrEmailIsExist: 'Email is already exist' +ErrSSLKeyNotFound: 'The private key file does not exist' +ErrSSLCertificateNotFound: 'The certificate file does not exist' +ErrSSLKeyFormat: 'Private key file format error, please use pem format' +ErrSSLCertificateFormat: 'Certificate file format error, please use pem format' #mysql ErrUserIsExist: "The current user already exists. Please enter a new user" diff --git a/backend/i18n/lang/zh-Hant.yaml b/backend/i18n/lang/zh-Hant.yaml index 09c95cd3f..449cc09e5 100644 --- a/backend/i18n/lang/zh-Hant.yaml +++ b/backend/i18n/lang/zh-Hant.yaml @@ -63,6 +63,10 @@ ErrSSLCannotDelete: "證書正在被網站使用,無法刪除" ErrAccountCannotDelete: "帳號關聯證書,無法刪除" ErrSSLApply: "證書續簽成功,openresty reload失敗,請檢查配置!" ErrEmailIsExist: '郵箱已存在' +ErrSSLKeyNotFound: '私鑰文件不存在' +ErrSSLCertificateNotFound: '證書文件不存在' +ErrSSLKeyFormat: '私鑰文件格式錯誤,請使用 pem 格式' +ErrSSLCertificateFormat: '證書文件格式錯誤,請使用 pem 格式' #mysql ErrUserIsExist: "當前用戶已存在,請重新輸入" diff --git a/backend/i18n/lang/zh.yaml b/backend/i18n/lang/zh.yaml index 62e682b74..2768a6e30 100644 --- a/backend/i18n/lang/zh.yaml +++ b/backend/i18n/lang/zh.yaml @@ -63,6 +63,10 @@ ErrSSLCannotDelete: "证书正在被网站使用,无法删除" ErrAccountCannotDelete: "账号关联证书,无法删除" ErrSSLApply: "证书续签成功,openresty reload失败,请检查配置!" ErrEmailIsExist: '邮箱已存在' +ErrSSLKeyNotFound: '私钥文件不存在' +ErrSSLCertificateNotFound: '证书文件不存在' +ErrSSLKeyFormat: '私钥文件格式错误,请使用 pem 格式' +ErrSSLCertificateFormat: '证书文件格式错误,请使用 pem 格式' #mysql ErrUserIsExist: "当前用户已存在,请重新输入" diff --git a/frontend/src/api/interface/app.ts b/frontend/src/api/interface/app.ts index b932f4279..e2a4b4cab 100644 --- a/frontend/src/api/interface/app.ts +++ b/frontend/src/api/interface/app.ts @@ -41,7 +41,6 @@ export namespace App { readme: string; params: AppParams; dockerCompose: string; - enbale: boolean; image: string; } diff --git a/frontend/src/components/file-list/index.vue b/frontend/src/components/file-list/index.vue index 42ea438a4..f70f8ab8e 100644 --- a/frontend/src/components/file-list/index.vue +++ b/frontend/src/components/file-list/index.vue @@ -55,7 +55,12 @@ @@ -100,7 +105,6 @@ const loading = ref(false); const paths = ref([]); const req = reactive({ path: '/', expand: true, page: 1, pageSize: 300 }); const selectRow = ref(); - const popoverVisible = ref(false); const props = defineProps({ @@ -133,6 +137,13 @@ const closePage = () => { selectRow.value = {}; }; +const disabledDir = (row: File.File) => { + if (!props.dir) { + return row.isDir; + } + return false; +}; + const open = async (row: File.File) => { if (row.isDir) { const name = row.name; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 4ff48910e..586ae5da8 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -1325,8 +1325,8 @@ const message = { HTTPToHTTPS: 'Access HTTP automatically jumps to HTTPS', HTTPAlso: 'HTTP can be accessed directly', sslConfig: 'SSL options', - disbaleHTTPS: 'Disable HTTPS', - disbaleHTTPSHelper: + disableHTTPS: 'Disable HTTPS', + disableHTTPSHelper: 'Disabling HTTPS will delete the certificate related configuration, Do you want to continue?', SSLHelper: 'Note: Do not use SSL certificates for illegal websites \n If HTTPS access cannot be used after opening, please check whether the security group has correctly released port 443', @@ -1417,7 +1417,12 @@ const message = { ipv6: 'Listen IPV6', leechReturnError: 'Please fill in the HTTP status code', selectAcme: 'Select Acme account', - localSSL: 'Imported', + imported: 'Imported', + importType: 'Import Type', + pasteSSL: 'Paste code', + localSSL: 'Select local file', + privateKeyPath: 'Private key file', + certificatePath: 'Certificate file', }, php: { short_open_tag: 'Short tag support', diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index fe36fbb08..44c6fb78e 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -1254,15 +1254,15 @@ const message = { manualSSL: '手動導入證書', select: '選擇', selectSSL: '選擇證書', - privateKey: '密鑰(KEY)', + privateKey: '私鑰(KEY)', certificate: '證書(PEM格式)', HTTPConfig: 'HTTP 選項', HTTPSOnly: '禁止 HTTP', HTTPToHTTPS: '訪問HTTP自動跳轉到HTTPS', HTTPAlso: 'HTTP可直接訪問', sslConfig: 'SSL 選項', - disbaleHTTPS: '禁用 HTTPS', - disbaleHTTPSHelper: '禁用 HTTPS會刪除證書相關配置,是否繼續?', + disableHTTPS: '禁用 HTTPS', + disableHTTPSHelper: '禁用 HTTPS會刪除證書相關配置,是否繼續?', SSLHelper: '註意:請勿將SSL證書用於非法網站 \n 如開啟後無法使用HTTPS訪問,請檢查安全組是否正確放行443端口', SSLConfig: '證書設置', SSLProConfig: 'SSL 協議設置', @@ -1348,7 +1348,12 @@ const message = { ipv6: '監聽 IPV6 端口', leechReturnError: '請填寫 HTTP 狀態碼', selectAcme: '選擇 Acme 賬號', - localSSL: '已導入', + imported: '已導入', + importType: '導入方式', + pasteSSL: '粘貼代碼', + localSSL: '選擇本地文件', + privateKeyPath: '私鑰文件', + certificatePath: '證書文件', }, php: { short_open_tag: '短標簽支持', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 37c17723e..92ba9e7be 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -1260,15 +1260,15 @@ const message = { manualSSL: '手动导入证书', select: '选择', selectSSL: '选择证书', - privateKey: '密钥(KEY)', + privateKey: '私钥(KEY)', certificate: '证书(PEM格式)', HTTPConfig: 'HTTP 选项', HTTPSOnly: '禁止 HTTP', HTTPToHTTPS: '访问HTTP自动跳转到HTTPS', HTTPAlso: 'HTTP可直接访问', sslConfig: 'SSL 选项', - disbaleHTTPS: '禁用 HTTPS', - disbaleHTTPSHelper: '禁用 HTTPS会删除证书相关配置,是否继续?', + disableHTTPS: '禁用 HTTPS', + disableHTTPSHelper: '禁用 HTTPS会删除证书相关配置,是否继续?', SSLHelper: '注意:请勿将SSL证书用于非法网站 \n 如开启后无法使用HTTPS访问,请检查安全组是否正确放行443端口', SSLConfig: '证书设置', SSLProConfig: 'SSL 协议设置', @@ -1354,7 +1354,12 @@ const message = { ipv6: '监听 IPV6 端口', leechReturnError: '请填写 HTTP 状态码', selectAcme: '选择 acme 账号', - localSSL: '已导入', + imported: '已导入', + importType: '导入方式', + pasteSSL: '粘贴代码', + localSSL: '选择本地文件', + privateKeyPath: '私钥文件', + certificatePath: '证书文件', }, php: { short_open_tag: '短标签支持', diff --git a/frontend/src/views/website/website/config/basic/https/index.vue b/frontend/src/views/website/website/config/basic/https/index.vue index 429717b6e..18d54e937 100644 --- a/frontend/src/views/website/website/config/basic/https/index.vue +++ b/frontend/src/views/website/website/config/basic/https/index.vue @@ -34,7 +34,7 @@ :placeholder="$t('website.selectAcme')" @change="listSSL" > - +
- - - - - + + + + + +
+ + + + + + +
+
+ + + + + + + + + + +
@@ -137,6 +161,7 @@ import i18n from '@/lang'; import { Rules } from '@/global/form-rules'; import { dateFormatSimple, getProvider } from '@/utils/util'; import { MsgSuccess } from '@/utils/message'; +import FileList from '@/components/file-list/index.vue'; const props = defineProps({ id: { @@ -154,8 +179,11 @@ const form = reactive({ websiteId: id.value, websiteSSLId: undefined, type: 'existed', + importType: 'paste', privateKey: '', certificate: '', + privateKeyPath: '', + certificatePath: '', httpConfig: 'HTTPToHTTPS', algorithm: 'EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5', @@ -169,6 +197,8 @@ const rules = ref({ type: [Rules.requiredSelect], privateKey: [Rules.requiredInput], certificate: [Rules.requiredInput], + privateKeyPath: [Rules.requiredInput], + certificatePath: [Rules.requiredInput], websiteSSLId: [Rules.requiredSelect], httpConfig: [Rules.requiredSelect], SSLProtocol: [Rules.requiredSelect], @@ -180,6 +210,13 @@ const sslReq = reactive({ acmeAccountID: 0, }); +const getPrivateKeyPath = (path: string) => { + form.privateKeyPath = path; +}; + +const getCertificatePath = (path: string) => { + form.certificatePath = path; +}; const listSSL = () => { sslReq.acmeAccountID = form.acmeAccountID; ListSSL(sslReq).then((res) => { @@ -217,6 +254,7 @@ const changeType = (type: string) => { const get = () => { GetHTTPSConfig(id.value).then((res) => { if (res.data) { + form.type = 'existed'; resData.value = res.data; form.enable = res.data.enable; if (res.data.httpConfig != '') { @@ -263,7 +301,7 @@ const changeEnable = (enable: boolean) => { listSSL(); } if (resData.value.enable && !enable) { - ElMessageBox.confirm(i18n.global.t('website.disbaleHTTPSHelper'), i18n.global.t('website.disbaleHTTPS'), { + ElMessageBox.confirm(i18n.global.t('website.disableHTTPSHelper'), i18n.global.t('website.disableHTTPS'), { confirmButtonText: i18n.global.t('commons.button.confirm'), cancelButtonText: i18n.global.t('commons.button.cancel'), type: 'error',