From 01cbdc98756821e5c641fa35386627e2cf96ae7d Mon Sep 17 00:00:00 2001 From: Jinnrry <19919556+Jinnrry@users.noreply.github.com> Date: Tue, 2 Jul 2024 21:02:00 +0800 Subject: [PATCH] V2.5.0 (#132) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 支持多用户 支持SSL证书支持DNS验证 增加mysql测试用例 修复DNS设置时的展示歧义 --- .github/workflows/unitTest.yml | 18 + .gitignore | 4 +- Makefile | 5 +- README.md | 3 + README_CN.md | 4 +- fe/src/components/GroupSettings.vue | 11 +- fe/src/components/HomeAside.vue | 5 +- fe/src/components/HomeHeader.vue | 34 +- fe/src/components/RuleSettings.vue | 16 +- fe/src/components/SecuritySettings.vue | 22 +- fe/src/components/UserManagement.vue | 181 ++ fe/src/http/http.js | 101 - fe/src/i18n/i18n.js | 30 +- fe/src/main.js | 109 +- fe/src/router/index.js | 4 + fe/src/views/EditerView.vue | 31 +- fe/src/views/EmailDetailView.vue | 8 +- fe/src/views/ListView.vue | 13 +- fe/src/views/LoginView.vue | 9 +- fe/src/views/SetupView.vue | 261 +- server/config/config.go | 8 +- server/config/config.json | 1 + server/consts/consts.go | 20 + server/controllers/email/detail.go | 8 - server/controllers/email/list.go | 10 +- server/controllers/email/send.go | 5 + server/controllers/login.go | 17 +- server/controllers/ping.go | 5 +- server/controllers/rule.go | 2 +- server/controllers/setup.go | 34 +- server/controllers/user.go | 174 ++ server/db/init.go | 91 +- server/dto/parsemail/email.go | 2 - server/dto/response/email.go | 8 + server/dto/response/response.go | 9 +- server/dto/rule.go | 2 + server/dto/tag.go | 6 +- server/go.mod | 238 +- server/go.sum | 2403 +++++++++++++++++- server/hooks/base.go | 8 +- server/hooks/framework/framework.go | 6 +- server/hooks/telegram_push/telegram_push.go | 17 +- server/hooks/web_push/web_push.go | 6 +- server/hooks/wechat_push/README.md | 20 +- server/hooks/wechat_push/wechat_push.go | 20 +- server/http_server/http_server.go | 64 +- server/http_server/https_server.go | 32 +- server/main_test.go | 435 +++- server/models/User.go | 4 +- server/models/auth.go | 11 - server/models/email.go | 25 +- server/models/user_email.go | 14 + server/models/version.go | 10 + server/pop3_server/action.go | 62 +- server/pop3_server/pop3server.go | 9 +- server/res_init/init.go | 2 +- server/services/auth/auth.go | 23 +- server/services/del_email/del_email.go | 47 +- server/services/detail/detail.go | 27 +- server/services/list/list.go | 80 +- server/services/list/list_test.go | 54 + server/services/rule/rule.go | 27 +- server/services/setup/db.go | 13 +- server/services/setup/dns.go | 6 +- server/services/setup/finish.go | 3 +- server/services/setup/ssl/dnsProvide.go | 67 + server/services/setup/ssl/dnsProvide_test.go | 36 + server/services/setup/ssl/ssl.go | 81 +- server/smtp_server/read_content.go | 102 +- server/smtp_server/read_content_test.go | 2 +- server/smtp_server/smtp.go | 3 +- server/utils/context/context.go | 1 + server/utils/password/encode_test.go | 11 + 73 files changed, 4712 insertions(+), 528 deletions(-) create mode 100644 fe/src/components/UserManagement.vue delete mode 100644 fe/src/http/http.js create mode 100644 server/consts/consts.go create mode 100644 server/controllers/user.go create mode 100644 server/dto/response/email.go delete mode 100644 server/models/auth.go create mode 100644 server/models/user_email.go create mode 100644 server/models/version.go create mode 100644 server/services/list/list_test.go create mode 100644 server/services/setup/ssl/dnsProvide.go create mode 100644 server/services/setup/ssl/dnsProvide_test.go create mode 100644 server/utils/password/encode_test.go diff --git a/.github/workflows/unitTest.yml b/.github/workflows/unitTest.yml index 2c79b83..376204f 100644 --- a/.github/workflows/unitTest.yml +++ b/.github/workflows/unitTest.yml @@ -19,6 +19,14 @@ jobs: test: name: Docker tests runs-on: ubuntu-latest + services: + mysql: + image: mysql + env: + MYSQL_DATABASE: pmail + MYSQL_ROOT_PASSWORD: githubTest + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + container: image: golang env: @@ -51,3 +59,13 @@ jobs: - name: Run Test run: make test + + - uses: actions/upload-artifact@v4 + with: + name: dbfile + path: server/config/pmail_temp.db + + + - name: Run Test Mysql + run: make test_mysql + diff --git a/.gitignore b/.gitignore index 01f21f4..c8b9f27 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ dist output pmail.db server/plugins -config \ No newline at end of file +config +*_KEY +AURORA_SECRET \ No newline at end of file diff --git a/Makefile b/Makefile index 7e126b2..82c32f3 100644 --- a/Makefile +++ b/Makefile @@ -49,4 +49,7 @@ package: clean cp README.md output/ test: - export setup_port=17888 && cd server && go test -v ./... \ No newline at end of file + export setup_port=17888 && cd server && go test -v ./... + +test_mysql: + export setup_port=17888 && cd server && go test -args "mysql" -v ./... \ No newline at end of file diff --git a/README.md b/README.md index c22d036..0f9dee1 100644 --- a/README.md +++ b/README.md @@ -146,3 +146,6 @@ The code is in `server` folder. [go to wiki](https://github.com/Jinnrry/PMail/wiki/%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91%E8%AF%B4%E6%98%8E) +# Thanks + +A special thanks to [Jetbrains](http://jetbrains.com/) for donating licenses to the project. \ No newline at end of file diff --git a/README_CN.md b/README_CN.md index 6868303..b593870 100644 --- a/README_CN.md +++ b/README_CN.md @@ -83,7 +83,7 @@ PMail是一个追求极简部署流程、极致资源占用的个人域名邮箱 "domain": "domain.com", // 你的域名 "webDomain": "mail.domain.com", // web域名 "dkimPrivateKeyPath": "config/dkim/dkim.priv", // dkim 私钥地址 - "sslType": "0", // ssl证书更新模式,0自动,1手动 + "sslType": "0", // ssl证书更新模式,0自动,HTTP模式,1手动、2自动,DNS模式 "SSLPrivateKeyPath": "config/ssl/private.key", // ssl 证书地址 "SSLPublicKeyPath": "config/ssl/public.crt", // ssl 证书地址 "dbDSN": "./config/pmail.db", // 数据库连接DSN @@ -149,4 +149,6 @@ SMTP端口: 25/465(SSL) [go to wiki](https://github.com/Jinnrry/PMail/wiki/%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91%E8%AF%B4%E6%98%8E) +# 致谢 +感谢 [Jetbrains](http://jetbrains.com/) 为本项目免费提供开发工具。 \ No newline at end of file diff --git a/fe/src/components/GroupSettings.vue b/fe/src/components/GroupSettings.vue index 953a6a5..c14429a 100644 --- a/fe/src/components/GroupSettings.vue +++ b/fe/src/components/GroupSettings.vue @@ -17,11 +17,12 @@ diff --git a/fe/src/components/RuleSettings.vue b/fe/src/components/RuleSettings.vue index c5b7101..e274242 100644 --- a/fe/src/components/RuleSettings.vue +++ b/fe/src/components/RuleSettings.vue @@ -98,7 +98,6 @@ + + + \ No newline at end of file diff --git a/fe/src/http/http.js b/fe/src/http/http.js deleted file mode 100644 index a326fe5..0000000 --- a/fe/src/http/http.js +++ /dev/null @@ -1,101 +0,0 @@ -// http/index.js -import axios from 'axios' -import router from "@/router"; //根路由对象 -import lang from '../i18n/i18n'; - -//创建axios的一个实例 -var $http = axios.create({ - baseURL: import.meta.env.VITE_APP_URL, //接口统一域名 - timeout: 60000, //设置超时 - headers: { - 'Content-Type': 'application/json;charset=UTF-8;', - 'Lang': lang.lang - } -}) - -//请求拦截器 -$http.interceptors.request.use((config) => { - //若请求方式为post,则将data参数转为JSON字符串 - if (config.method === 'POST') { - config.data = JSON.stringify(config.data); - } - return config; -}, (error) => - // 对请求错误做些什么 - Promise.reject(error)); - -//响应拦截器 -$http.interceptors.response.use((response) => { - //响应成功 - if (response.data.errorNo == 403) { - router.replace({ - path: '/login', - query: { - redirect: router.currentRoute.fullPath - } - }) - } - //响应成功 - if (response.data.errorNo == 402) { - router.replace({ - path: '/setup', - query: { - redirect: router.currentRoute.fullPath - } - }) - } - return response.data; -}, (error) => { - //响应错误 - if (error.response && error.response.status) { - const status = error.response.status - let message = "" - switch (status) { - case 400: - message = '请求错误'; - break; - case 401: - message = '请求错误'; - break; - case 403: - router.replace({ - path: '/login', - query: { - redirect: router.currentRoute.fullPath - } - }) - break; - case 404: - message = '请求地址出错'; - break; - case 408: - message = '请求超时'; - break; - case 500: - message = '服务器内部错误!'; - break; - case 501: - message = '服务未实现!'; - break; - case 502: - message = '网关错误!'; - break; - case 503: - message = '服务不可用!'; - break; - case 504: - message = '网关超时!'; - break; - case 505: - message = 'HTTP版本不受支持'; - break; - default: - message = '请求失败' - } - return Promise.reject(error); - } - return Promise.reject(error); -}); - - -export default $http; \ No newline at end of file diff --git a/fe/src/i18n/i18n.js b/fe/src/i18n/i18n.js index 4b26e7c..aa25e37 100644 --- a/fe/src/i18n/i18n.js +++ b/fe/src/i18n/i18n.js @@ -1,4 +1,12 @@ var lang = { + "logout": "Logout", + "resetPwd": "Reset the account password", + "disabled": "Disabled", + "enabled": "Enabled", + "newUser": "Create Account", + "editUser": "Edit Account", + "user_name": "User Name", + "user_management": "user management", "lang": "en", "submit": "submit", "compose": "compose", @@ -55,6 +63,12 @@ var lang = { "web_domain": "Web Domain", "dns_desc": "Please add the following information to your DNS records", "ssl_auto": "Automatically configure SSL certificates (recommended)", + "wait_desc":"HTTP challenge mode completes in approximately 1 minute and DNS API challenge mode completes in approximately 10 minutes.", + "ssl_challenge_type":"Challenge Type", + "ssl_auto_http":"Http Request", + "ssl_auto_dns":"DNS Records", + "challenge_typ_desc":"If PMail uses port 80 directly, it is recommended that you use the HTTP challenge method. If PMail does not use port 80 directly, it is recommended to use DNS challenge method, DNS API key to ask your domain name service provider to apply.", + "oomain_service_provider":"Domain Name Service Provider", "ssl_manuallyf": "Manually configure an SSL certificate", "ssl_key_path": "ssl key file path", "ssl_crt_path": "ssl crt file path", @@ -92,11 +106,19 @@ var lang = { var zhCN = { + "logout": "注销", + "resetPwd": "重置账号密码", + "disabled": "禁用", + "enabled": "启用", + "user_name": "用户名", + "newUser": "新增用户", + "editUser": "编辑用户", + "user_management": "用户管理", "lang": "zhCn", "submit": "提交", "compose": "发件", "new": "新", - "account": "用户名", + "account": "账号", "password": "密码", "login": "登录", "search": "搜索邮件", @@ -148,7 +170,13 @@ var zhCN = { "web_domain": "Web域名地址", "dns_desc": "请将以下信息添加到DNS记录中", "ssl_auto": "自动配置SSL证书(推荐)", + "oomain_service_provider":"域名服务商", + "ssl_auto_http":"HTTP请求", + "ssl_auto_dns":"DNS记录", + "ssl_challenge_type":"验证方式", "ssl_manuallyf": "手动配置SSL证书", + "challenge_typ_desc":"如果PMail直接使用80端口,建议使用HTTP验证方式。如果PMail没有直接使用80端口,建议使用DNS验证方式,DNS API Key找你的域名服务商申请", + "wait_desc":"HTTP验证模式大约1分钟完成,DNS API验证模式大约15分钟完成。", "ssl_key_path": "ssl key文件位置", "ssl_crt_path": "ssl crt文件位置", "group_settings": "分组", diff --git a/fe/src/main.js b/fe/src/main.js index 599d3d6..e656f28 100644 --- a/fe/src/main.js +++ b/fe/src/main.js @@ -3,14 +3,119 @@ import 'element-plus/dist/index.css' import { createApp } from 'vue' import { createPinia } from 'pinia' - +import {ref} from 'vue' import App from './App.vue' import router from './router' const app = createApp(App) - +app.config.globalProperties.$isLogin = ref(true) +app.config.globalProperties.$userInfos = ref({}) app.use(createPinia()) app.use(router) + + +import axios from 'axios' +import lang from './i18n/i18n'; +//创建axios的一个实例 +var $http = axios.create({ + baseURL: import.meta.env.VITE_APP_URL, //接口统一域名 + timeout: 60000, //设置超时 + headers: { + 'Content-Type': 'application/json;charset=UTF-8;', + 'Lang': lang.lang + } +}) + +//请求拦截器 +$http.interceptors.request.use((config) => { + //若请求方式为post,则将data参数转为JSON字符串 + if (config.method === 'POST') { + config.data = JSON.stringify(config.data); + } + return config; +}, (error) => + // 对请求错误做些什么 + Promise.reject(error)); + +//响应拦截器 +$http.interceptors.response.use((response) => { + //响应成功 + if (response.data.errorNo == 403) { + app.config.globalProperties.$isLogin.value = false + + router.replace({ + path: '/login', + query: { + redirect: router.currentRoute.fullPath + } + }) + } + //响应成功 + if (response.data.errorNo == 402) { + router.replace({ + path: '/setup', + query: { + redirect: router.currentRoute.fullPath + } + }) + } + return response.data; +}, (error) => { + //响应错误 + if (error.response && error.response.status) { + const status = error.response.status + let message = "" + switch (status) { + case 400: + message = '请求错误'; + break; + case 401: + message = '请求错误'; + break; + case 403: + router.replace({ + path: '/login', + query: { + redirect: router.currentRoute.fullPath + } + }) + break; + case 404: + message = '请求地址出错'; + break; + case 408: + message = '请求超时'; + break; + case 500: + message = '服务器内部错误!'; + break; + case 501: + message = '服务未实现!'; + break; + case 502: + message = '网关错误!'; + break; + case 503: + message = '服务不可用!'; + break; + case 504: + message = '网关超时!'; + break; + case 505: + message = 'HTTP版本不受支持'; + break; + default: + message = '请求失败' + } + return Promise.reject(error); + } + return Promise.reject(error); +}); + + +// 注册到全局 +app.config.globalProperties.$http = $http + app.mount('#app') diff --git a/fe/src/router/index.js b/fe/src/router/index.js index 26bc09b..8b8ca13 100644 --- a/fe/src/router/index.js +++ b/fe/src/router/index.js @@ -42,4 +42,8 @@ const router = createRouter({ ] }) + + + + export default router diff --git a/fe/src/views/EditerView.vue b/fe/src/views/EditerView.vue index c043f88..b523e8d 100644 --- a/fe/src/views/EditerView.vue +++ b/fe/src/views/EditerView.vue @@ -2,7 +2,7 @@
- + @@ -76,8 +76,6 @@