Skip to content

Commit de336b5

Browse files
author
Kerwin
committed
test: 优化邮箱新增测试
chore: 优化首次打开有些慢的问题
1 parent e8b7de9 commit de336b5

File tree

15 files changed

+359
-225
lines changed

15 files changed

+359
-225
lines changed

service/src/index.ts

Lines changed: 53 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ import { Status } from './storage/model'
1111
import { clearChat, createChatRoom, createUser, deleteChat, deleteChatRoom, existsChatRoom, getChat, getChatRooms, getChats, getUser, getUserById, insertChat, renameChatRoom, updateChat, updateConfig, updateUserInfo, verifyUser } from './storage/mongo'
1212
import { limiter } from './middleware/limiter'
1313
import { isNotEmptyString } from './utils/is'
14-
import { sendMail } from './utils/mail'
14+
import { sendTestMail, sendVerifyMail } from './utils/mail'
1515
import { checkUserVerify, getUserVerifyUrl, md5 } from './utils/security'
16+
import { rootAuth } from './middleware/rootAuth'
1617

1718
const app = express()
1819
const router = express.Router()
@@ -182,41 +183,46 @@ router.post('/chat-process', [auth, limiter], async (req, res) => {
182183
})
183184

184185
router.post('/user-register', async (req, res) => {
185-
const { username, password } = req.body as { username: string; password: string }
186-
const config = await getCacheConfig()
187-
if (!config.siteConfig.registerEnabled) {
188-
res.send({ status: 'Fail', message: '注册账号功能未启用 | Register account is disabled!', data: null })
189-
return
190-
}
191-
if (isNotEmptyString(config.siteConfig.registerMails)) {
192-
let allowSuffix = false
193-
const emailSuffixs = config.siteConfig.registerMails.split(',')
194-
for (let index = 0; index < emailSuffixs.length; index++) {
195-
const element = emailSuffixs[index]
196-
allowSuffix = username.toLowerCase().endsWith(element)
197-
if (allowSuffix)
198-
break
199-
}
200-
if (!allowSuffix) {
201-
res.send({ status: 'Fail', message: '该邮箱后缀不支持 | The email service provider is not allowed', data: null })
186+
try {
187+
const { username, password } = req.body as { username: string; password: string }
188+
const config = await getCacheConfig()
189+
if (!config.siteConfig.registerEnabled) {
190+
res.send({ status: 'Fail', message: '注册账号功能未启用 | Register account is disabled!', data: null })
202191
return
203192
}
204-
}
193+
if (isNotEmptyString(config.siteConfig.registerMails)) {
194+
let allowSuffix = false
195+
const emailSuffixs = config.siteConfig.registerMails.split(',')
196+
for (let index = 0; index < emailSuffixs.length; index++) {
197+
const element = emailSuffixs[index]
198+
allowSuffix = username.toLowerCase().endsWith(element)
199+
if (allowSuffix)
200+
break
201+
}
202+
if (!allowSuffix) {
203+
res.send({ status: 'Fail', message: '该邮箱后缀不支持 | The email service provider is not allowed', data: null })
204+
return
205+
}
206+
}
205207

206-
const user = await getUser(username)
207-
if (user != null) {
208-
res.send({ status: 'Fail', message: '邮箱已存在 | The email exists', data: null })
209-
return
210-
}
211-
const newPassword = md5(password)
212-
await createUser(username, newPassword)
208+
const user = await getUser(username)
209+
if (user != null) {
210+
res.send({ status: 'Fail', message: '邮箱已存在 | The email exists', data: null })
211+
return
212+
}
213+
const newPassword = md5(password)
214+
await createUser(username, newPassword)
213215

214-
if (username.toLowerCase() === process.env.ROOT_USER) {
215-
res.send({ status: 'Success', message: '注册成功 | Register success', data: null })
216+
if (username.toLowerCase() === process.env.ROOT_USER) {
217+
res.send({ status: 'Success', message: '注册成功 | Register success', data: null })
218+
}
219+
else {
220+
await sendVerifyMail(username, await getUserVerifyUrl(username))
221+
res.send({ status: 'Success', message: '注册成功, 去邮箱中验证吧 | Registration is successful, you need to go to email verification', data: null })
222+
}
216223
}
217-
else {
218-
await sendMail(username, await getUserVerifyUrl(username))
219-
res.send({ status: 'Success', message: '注册成功, 去邮箱中验证吧 | Registration is successful, you need to go to email verification', data: null })
224+
catch (error) {
225+
res.send({ status: 'Fail', message: error.message, data: null })
220226
}
221227
})
222228

@@ -307,18 +313,13 @@ router.post('/verify', async (req, res) => {
307313
}
308314
})
309315

310-
router.post('/setting-base', auth, async (req, res) => {
316+
router.post('/setting-base', rootAuth, async (req, res) => {
311317
try {
312318
const { apiKey, apiModel, apiBaseUrl, accessToken, timeoutMs, socksProxy, httpsProxy } = req.body as Config
313-
const userId = new ObjectId(req.headers.userId.toString())
314319

315320
if (apiKey == null && accessToken == null)
316321
throw new Error('Missing OPENAI_API_KEY or OPENAI_ACCESS_TOKEN environment variable.')
317322

318-
const user = await getUserById(userId)
319-
if (user == null || user.status !== Status.Normal || user.email.toLowerCase() !== process.env.ROOT_USER)
320-
throw new Error('无权限 | No permission.')
321-
322323
const thisConfig = await getOriginConfig()
323324
thisConfig.apiKey = apiKey
324325
thisConfig.apiModel = apiModel
@@ -338,14 +339,9 @@ router.post('/setting-base', auth, async (req, res) => {
338339
}
339340
})
340341

341-
router.post('/setting-site', auth, async (req, res) => {
342+
router.post('/setting-site', rootAuth, async (req, res) => {
342343
try {
343344
const config = req.body as SiteConfig
344-
const userId = new ObjectId(req.headers.userId.toString())
345-
346-
const user = await getUserById(userId)
347-
if (user == null || user.status !== Status.Normal || user.email.toLowerCase() !== process.env.ROOT_USER)
348-
throw new Error('无权限 | No permission.')
349345

350346
const thisConfig = await getOriginConfig()
351347
thisConfig.siteConfig = config
@@ -358,14 +354,9 @@ router.post('/setting-site', auth, async (req, res) => {
358354
}
359355
})
360356

361-
router.post('/setting-mail', auth, async (req, res) => {
357+
router.post('/setting-mail', rootAuth, async (req, res) => {
362358
try {
363359
const config = req.body as MailConfig
364-
const userId = new ObjectId(req.headers.userId.toString())
365-
366-
const user = await getUserById(userId)
367-
if (user == null || user.status !== Status.Normal || user.email.toLowerCase() !== process.env.ROOT_USER)
368-
throw new Error('无权限 | No permission.')
369360

370361
const thisConfig = await getOriginConfig()
371362
thisConfig.mailConfig = config
@@ -378,6 +369,19 @@ router.post('/setting-mail', auth, async (req, res) => {
378369
}
379370
})
380371

372+
router.post('/mail-test', rootAuth, async (req, res) => {
373+
try {
374+
const config = req.body as MailConfig
375+
const userId = new ObjectId(req.headers.userId as string)
376+
const user = await getUserById(userId)
377+
await sendTestMail(user.email, config)
378+
res.send({ status: 'Success', message: '发送成功 | Successfully', data: null })
379+
}
380+
catch (error) {
381+
res.send({ status: 'Fail', message: error.message, data: null })
382+
}
383+
})
384+
381385
app.use('', router)
382386
app.use('/api', router)
383387
app.set('trust proxy', 1)

service/src/middleware/rootAuth.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import jwt from 'jsonwebtoken'
2+
import { ObjectId } from 'mongodb'
3+
import { Status } from '../storage/model'
4+
import { getUserById } from '../storage/mongo'
5+
import { getCacheConfig } from '../storage/config'
6+
7+
const rootAuth = async (req, res, next) => {
8+
const config = await getCacheConfig()
9+
if (config.siteConfig.loginEnabled) {
10+
try {
11+
const token = req.header('Authorization').replace('Bearer ', '')
12+
const info = jwt.verify(token, config.siteConfig.loginSalt.trim())
13+
req.headers.userId = info.userId
14+
const user = await getUserById(new ObjectId(info.userId))
15+
if (user == null || user.status !== Status.Normal || user.email.toLowerCase() !== process.env.ROOT_USER)
16+
res.send({ status: 'Fail', message: '无权限 | No permission.', data: null })
17+
else
18+
next()
19+
}
20+
catch (error) {
21+
res.send({ status: 'Unauthorized', message: error.message ?? 'Please authenticate.', data: null })
22+
}
23+
}
24+
else {
25+
res.send({ status: 'Fail', message: '无权限 | No permission.', data: null })
26+
}
27+
}
28+
29+
export { rootAuth }

service/src/utils/mail.ts

Lines changed: 29 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -1,160 +1,41 @@
1+
import * as fs from 'fs'
2+
import * as path from 'path'
13
import nodemailer from 'nodemailer'
4+
import type { MailConfig } from '../storage/model'
25
import { getCacheConfig } from '../storage/config'
36

4-
export async function sendMail(toMail: string, verifyUrl: string) {
7+
export async function sendVerifyMail(toMail: string, verifyUrl: string) {
58
const config = (await getCacheConfig())
9+
10+
const templatesPath = path.join(__dirname, 'templates')
11+
const mailTemplatePath = path.join(templatesPath, 'mail.template.html')
12+
let mailHtml = fs.readFileSync(mailTemplatePath, 'utf8')
13+
mailHtml = mailHtml.replace(/\${VERIFY_URL}/g, verifyUrl)
14+
mailHtml = mailHtml.replace(/\${SITE_TITLE}/g, config.siteConfig.siteTitle)
15+
sendMail(toMail, `${config.siteConfig.siteTitle} 账号验证`, mailHtml, config.mailConfig)
16+
}
17+
18+
export async function sendTestMail(toMail: string, config: MailConfig) {
19+
return sendMail(toMail, '测试邮件|Test mail', '这是一封测试邮件|This is test mail', config)
20+
}
21+
22+
async function sendMail(toMail: string, subject: string, html: string, config: MailConfig) {
623
const mailOptions = {
7-
from: config.mailConfig.smtpUserName, // sender address
8-
to: toMail, // list of receivers
9-
subject: `${config.siteConfig.siteTitle} 账号验证`, // Subject line
10-
html: `
11-
<div class="page flex-col">
12-
<div class="box_3 flex-col" style="
13-
display: flex;
14-
position: relative;
15-
width: 100%;
16-
height: 206px;
17-
background: #ef859d2e;
18-
top: 0;
19-
left: 0;
20-
justify-content: center;
21-
"><div class="section_1 flex-col" style="
22-
background-image: url(&quot;https://ghproxy.com/https://raw.githubusercontent.com/Chanzhaoyu/chatgpt-web/main/src/assets/avatar.jpg&quot;);
23-
position: absolute;
24-
width: 152px;
25-
height: 152px;
26-
display: flex;
27-
top: 130px;
28-
background-size: cover;
29-
border-radius: 50%;
30-
margin: 10px;
31-
"></div></div>
32-
<div class="box_4 flex-col" style="
33-
margin-top: 92px;
34-
display: flex;
35-
flex-direction: column;
36-
align-items: center;
37-
">
38-
<div class="text-group_5 flex-col justify-between" style="
39-
display: flex;
40-
flex-direction: column;
41-
align-items: center;
42-
margin: 0 20px;
43-
">
44-
<span class="text_1" style="
45-
font-size: 26px;
46-
font-family: PingFang-SC-Bold, PingFang-SC;
47-
font-weight: bold;
48-
color: #000000;
49-
line-height: 37px;
50-
text-align: center;
51-
"><target="_blank" style="text-decoration: none; color: #0088cc;">${config.siteConfig.siteTitle}</a> 账号验证</span>
52-
53-
<div class="box_2 flex-row" style="
54-
margin: 0 20px;
55-
min-height: 128px;
56-
background: #F7F7F7;
57-
border-radius: 12px;
58-
margin-top: 34px;
59-
display: flex;
60-
flex-direction: column;
61-
align-items: flex-start;
62-
padding: 32px 16px;
63-
width: calc(100% - 40px);
64-
">
65-
66-
<div class="text-wrapper_4 flex-col justify-between" style="
67-
display: flex;
68-
flex-direction: column;
69-
margin-left: 30px;
70-
margin-bottom: 16px;
71-
">
72-
<hr>
73-
<span class="text_3" style="
74-
<div style="font-family: Arial, sans-serif; font-size: 16px; color: #333;">
75-
<h1 style="color: #0088cc;">
76-
感谢您使用
77-
<target="_blank" style="text-decoration: none; color: #0088cc;">${config.siteConfig.siteTitle}</a>,
78-
您的邮箱验证链接为(12小时内有效):
79-
</span>
80-
</div><hr style="
81-
display: flex;
82-
position: relative;
83-
border: 1px dashed #ef859d2e;
84-
box-sizing: content-box;
85-
height: 0px;
86-
overflow: visible;
87-
width: 100%;
88-
"><div class="text-wrapper_4 flex-col justify-between" style="
89-
display: flex;
90-
flex-direction: column;
91-
margin-left: 30px;
92-
">
93-
<hr>
94-
</h1>
95-
<p style="margin-top: 20px;">
96-
请点击以下按钮进行验证:
97-
<span class="text_4" style="
98-
margin-top: 6px;
99-
margin-right: 22px;
100-
font-size: 16px;
101-
font-family: PingFangSC-Regular, PingFang SC;
102-
font-weight: 400;
103-
color: #000000;
104-
line-height: 22px;
105-
"></span>
106-
</div>
107-
108-
<a class="text-wrapper_2 flex-col" style="
109-
min-width: 106px;
110-
height: 38px;
111-
background: #ef859d38;
112-
border-radius: 32px;
113-
display: flex;
114-
align-items: center;
115-
justify-content: center;
116-
text-decoration: none;
117-
margin: auto;
118-
margin-top: 32px;
119-
" href="${verifyUrl}">
120-
<span class="text_5" style="
121-
color: #DB214B;
122-
">点我验证</span>
123-
</a>
124-
</div>
125-
<div class="text-group_6 flex-col justify-between" style="
126-
display: flex;
127-
flex-direction: column;
128-
align-items: center;
129-
margin-top: 34px;
130-
">
131-
<span class="text_6" style="
132-
height: 17px;
133-
font-size: 12px;
134-
font-family: PingFangSC-Regular, PingFang SC;
135-
font-weight: 400;
136-
color: #00000045;
137-
line-height: 17px;
138-
">此邮件由服务器自动发出,直接回复无效。</span>
139-
</div>
140-
</div>
141-
</div>
142-
`, // html body
24+
from: config.smtpUserName,
25+
to: toMail,
26+
subject,
27+
html,
14328
}
14429

14530
const transporter = nodemailer.createTransport({
146-
host: config.mailConfig.smtpHost,
147-
port: config.mailConfig.smtpPort,
148-
secure: config.mailConfig.smtpTsl,
31+
host: config.smtpHost,
32+
port: config.smtpPort,
33+
secure: config.smtpTsl,
14934
auth: {
150-
user: config.mailConfig.smtpUserName,
151-
pass: config.mailConfig.smtpPassword,
35+
user: config.smtpUserName,
36+
pass: config.smtpPassword,
15237
},
15338
})
154-
transporter.sendMail(mailOptions, (error, info) => {
155-
if (error)
156-
throw error
157-
else
158-
return info.messageId
159-
})
39+
const info = await transporter.sendMail(mailOptions)
40+
return info.messageId
16041
}

0 commit comments

Comments
 (0)