Skip to content

Commit 3333919

Browse files
author
Kerwin
committed
feat: 修改验证账号逻辑,需要管理员开通,修复一些bug
2 parents f54b7c7 + b814d3d commit 3333919

File tree

12 files changed

+448
-12
lines changed

12 files changed

+448
-12
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
version: '3'
2+
3+
services:
4+
mongo:
5+
image: mongo
6+
container_name: mongodb
7+
restart: always
8+
ports:
9+
- '27017:27017'
10+
volumes:
11+
- ./mongodb:/data/db
12+
environment:
13+
MONGO_INITDB_ROOT_USERNAME: chatgpt
14+
MONGO_INITDB_ROOT_PASSWORD: password
15+
MONGO_INITDB_DATABASE: chatgpt

service/src/index.ts

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,31 @@ import { auth } from './middleware/auth'
77
import { clearConfigCache, getCacheConfig, getOriginConfig } from './storage/config'
88
import type { ChatOptions, Config, MailConfig, SiteConfig, UserInfo } from './storage/model'
99
import { Status } from './storage/model'
10-
import { clearChat, createChatRoom, createUser, deleteAllChatRooms, deleteChat, deleteChatRoom, existsChatRoom, getChat, getChatRooms, getChats, getUser, getUserById, insertChat, renameChatRoom, updateChat, updateConfig, updateUserInfo, verifyUser } from './storage/mongo'
10+
import {
11+
clearChat,
12+
createChatRoom,
13+
createUser,
14+
deleteAllChatRooms,
15+
deleteChat,
16+
deleteChatRoom,
17+
existsChatRoom,
18+
getChat,
19+
getChatRooms,
20+
getChats,
21+
getUser,
22+
getUserById,
23+
insertChat,
24+
renameChatRoom,
25+
updateChat,
26+
updateConfig,
27+
updateUserInfo,
28+
verifyUser,
29+
verifyUserAdmin,
30+
} from './storage/mongo'
1131
import { limiter } from './middleware/limiter'
1232
import { isNotEmptyString } from './utils/is'
13-
import { sendTestMail, sendVerifyMail } from './utils/mail'
14-
import { checkUserVerify, getUserVerifyUrl, md5 } from './utils/security'
33+
import { sendNoticeMail, sendTestMail, sendVerifyMail, sendVerifyMailAdmin } from './utils/mail'
34+
import { checkUserVerify, checkUserVerifyAdmin, getUserVerifyUrl, getUserVerifyUrlAdmin, md5 } from './utils/security'
1535
import { rootAuth } from './middleware/rootAuth'
1636

1737
const app = express()
@@ -321,6 +341,8 @@ router.post('/user-login', async (req, res) => {
321341
|| user.password !== md5(password)) {
322342
if (user != null && user.status === Status.PreVerify)
323343
throw new Error('请去邮箱中验证 | Please verify in the mailbox')
344+
if (user != null && user.status === Status.AdminVerify)
345+
throw new Error('请等待管理员开通 | Please wait for the admin to activate')
324346
throw new Error('用户不存在或密码错误 | User does not exist or incorrect password.')
325347
}
326348
const config = await getCacheConfig()
@@ -360,8 +382,34 @@ router.post('/verify', async (req, res) => {
360382
if (!token)
361383
throw new Error('Secret key is empty')
362384
const username = await checkUserVerify(token)
385+
const user = await getUser(username)
386+
if (user != null && user.status === Status.Normal) {
387+
res.send({ status: 'Fail', message: '邮箱已存在 | The email exists', data: null })
388+
return
389+
}
363390
await verifyUser(username)
364-
res.send({ status: 'Success', message: '验证成功 | Verify successfully', data: null })
391+
await sendVerifyMailAdmin(username, await getUserVerifyUrlAdmin(username))
392+
res.send({ status: 'Success', message: '验证成功, 请等待管理员开通 | Verify successfully, Please wait for the admin to activate', data: null })
393+
}
394+
catch (error) {
395+
res.send({ status: 'Fail', message: error.message, data: null })
396+
}
397+
})
398+
399+
router.post('/verifyadmin', async (req, res) => {
400+
try {
401+
const { token } = req.body as { token: string }
402+
if (!token)
403+
throw new Error('Secret key is empty')
404+
const username = await checkUserVerifyAdmin(token)
405+
const user = await getUser(username)
406+
if (user != null && user.status === Status.Normal) {
407+
res.send({ status: 'Fail', message: '邮箱已存在 | The email exists', data: null })
408+
return
409+
}
410+
await verifyUserAdmin(username)
411+
await sendNoticeMail(username)
412+
res.send({ status: 'Success', message: '开通成功 | Activate successfully', data: null })
365413
}
366414
catch (error) {
367415
res.send({ status: 'Fail', message: error.message, data: null })

service/src/middleware/auth.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import jwt from 'jsonwebtoken'
22
import { getCacheConfig } from '../storage/config'
3+
import { getUserById } from '../storage/mongo'
4+
import { Status } from '../storage/model'
35

46
const auth = async (req, res, next) => {
57
const config = await getCacheConfig()
@@ -8,7 +10,11 @@ const auth = async (req, res, next) => {
810
const token = req.header('Authorization').replace('Bearer ', '')
911
const info = jwt.verify(token, config.siteConfig.loginSalt.trim())
1012
req.headers.userId = info.userId
11-
next()
13+
const user = await getUserById(info.userId)
14+
if (user == null || user.status !== Status.Normal)
15+
throw new Error('用户不存在 | User does not exist.')
16+
else
17+
next()
1218
}
1319
catch (error) {
1420
res.send({ status: 'Unauthorized', message: error.message ?? 'Please authenticate.', data: null })

service/src/storage/model.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export enum Status {
66
InversionDeleted = 2,
77
ResponseDeleted = 3,
88
PreVerify = 4,
9+
AdminVerify = 5,
910
}
1011

1112
export class UserInfo {

service/src/storage/mongo.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import * as dotenv from 'dotenv'
12
import { MongoClient, ObjectId } from 'mongodb'
23
import { ChatInfo, ChatRoom, Status, UserInfo } from './model'
34
import type { ChatOptions, Config } from './model'
45

6+
dotenv.config()
57
const url = process.env.MONGODB_URL
68
const client = new MongoClient(url)
79
const chatCol = client.db('chatgpt').collection('chat')
@@ -149,6 +151,11 @@ export async function getUserById(userId: string): Promise<UserInfo> {
149151
}
150152

151153
export async function verifyUser(email: string) {
154+
email = email.toLowerCase()
155+
return await userCol.updateOne({ email }, { $set: { status: Status.AdminVerify, verifyTime: new Date().toLocaleString() } })
156+
}
157+
158+
export async function verifyUserAdmin(email: string) {
152159
email = email.toLowerCase()
153160
return await userCol.updateOne({ email }, { $set: { status: Status.Normal, verifyTime: new Date().toLocaleString() } })
154161
}

service/src/utils/mail.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,29 @@ export async function sendVerifyMail(toMail: string, verifyUrl: string) {
1515
sendMail(toMail, `${config.siteConfig.siteTitle} 账号验证`, mailHtml, config.mailConfig)
1616
}
1717

18+
export async function sendVerifyMailAdmin(toMail: string, verifyUrl: string) {
19+
const config = (await getCacheConfig())
20+
21+
const templatesPath = path.join(__dirname, 'templates')
22+
const mailTemplatePath = path.join(templatesPath, 'mail.admin.template.html')
23+
let mailHtml = fs.readFileSync(mailTemplatePath, 'utf8')
24+
mailHtml = mailHtml.replace(/\${TO_MAIL}/g, toMail)
25+
mailHtml = mailHtml.replace(/\${VERIFY_URL}/g, verifyUrl)
26+
mailHtml = mailHtml.replace(/\${SITE_TITLE}/g, config.siteConfig.siteTitle)
27+
sendMail(config.mailConfig.smtpUserName, `${config.siteConfig.siteTitle} 账号申请`, mailHtml, config.mailConfig)
28+
}
29+
30+
export async function sendNoticeMail(toMail: string) {
31+
const config = (await getCacheConfig())
32+
33+
const templatesPath = path.join(__dirname, 'templates')
34+
const mailTemplatePath = path.join(templatesPath, 'mail.notice.template.html')
35+
let mailHtml = fs.readFileSync(mailTemplatePath, 'utf8')
36+
mailHtml = mailHtml.replace(/\${SITE_DOMAIN}/g, config.siteConfig.siteDomain)
37+
mailHtml = mailHtml.replace(/\${SITE_TITLE}/g, config.siteConfig.siteTitle)
38+
sendMail(toMail, `${config.siteConfig.siteTitle} 账号开通`, mailHtml, config.mailConfig)
39+
}
40+
1841
export async function sendTestMail(toMail: string, config: MailConfig) {
1942
return sendMail(toMail, '测试邮件|Test mail', '这是一封测试邮件|This is test mail', config)
2043
}

service/src/utils/security.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,28 @@ export function checkUserVerify(verify: string) {
3232
return username
3333
throw new Error('Verify failed')
3434
}
35+
36+
// 可以换 aes 等方式
37+
export async function getUserVerifyUrlAdmin(username: string) {
38+
const sign = getUserVerifyAdmin(username)
39+
const config = await getCacheConfig()
40+
return `${config.siteConfig.siteDomain}/#/chat/?verifytokenadmin=${sign}`
41+
}
42+
43+
function getUserVerifyAdmin(username: string) {
44+
const expired = new Date().getTime() + (12 * 60 * 60 * 1000)
45+
const sign = `${username}|${process.env.ROOT_USER}-${expired}`
46+
return `${sign}-${md5(sign)}`
47+
}
48+
49+
export function checkUserVerifyAdmin(verify: string) {
50+
const vs = verify.split('-')
51+
const sign = vs[vs.length - 1]
52+
const expired = vs[vs.length - 2]
53+
vs.splice(vs.length - 2, 2)
54+
const username = vs.join('-')
55+
// 简单点没校验有效期
56+
if (sign === md5(`${username}-${expired}`))
57+
return username.split('|')[0]
58+
throw new Error('Verify failed')
59+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
<html>
2+
<head> </head>
3+
<body>
4+
<div class="page flex-col">
5+
<div class="box_3 flex-col" style="
6+
display: flex;
7+
position: relative;
8+
width: 100%;
9+
height: 206px;
10+
background: #ef859d2e;
11+
top: 0;
12+
left: 0;
13+
justify-content: center;
14+
">
15+
<div class="section_1 flex-col" style="
16+
background-image: url(&quot;https://ghproxy.com/https://raw.githubusercontent.com/Chanzhaoyu/chatgpt-web/main/src/assets/avatar.jpg&quot;);
17+
position: absolute;
18+
width: 152px;
19+
height: 152px;
20+
display: flex;
21+
top: 130px;
22+
background-size: cover;
23+
border-radius: 50%;
24+
margin: 10px;
25+
"></div>
26+
</div>
27+
<div class="box_4 flex-col" style="
28+
margin-top: 92px;
29+
display: flex;
30+
flex-direction: column;
31+
align-items: center;
32+
">
33+
<div class="text-group_5 flex-col justify-between" style="
34+
display: flex;
35+
flex-direction: column;
36+
align-items: center;
37+
margin: 0 20px;
38+
">
39+
<span class="text_1" style="
40+
font-size: 26px;
41+
font-family: PingFang-SC-Bold, PingFang-SC;
42+
font-weight: bold;
43+
color: #000000;
44+
line-height: 37px;
45+
text-align: center;
46+
">
47+
<target="_blank" style="text-decoration: none; color: #0088cc;">${SITE_TITLE}</a> 账号申请
48+
</span>
49+
50+
<div class="box_2 flex-row" style="
51+
margin: 0 20px;
52+
min-height: 128px;
53+
background: #F7F7F7;
54+
border-radius: 12px;
55+
margin-top: 34px;
56+
display: flex;
57+
flex-direction: column;
58+
align-items: flex-start;
59+
padding: 32px 16px;
60+
width: calc(100% - 40px);
61+
">
62+
63+
<div class="text-wrapper_4 flex-col justify-between" style="
64+
display: flex;
65+
flex-direction: column;
66+
margin-left: 30px;
67+
margin-bottom: 16px;
68+
">
69+
<hr>
70+
<span class="text_3" style="
71+
<div style=" font-family: Arial, sans-serif; font-size: 16px; color: #333;">
72+
<h1 style="color: #0088cc;">
73+
账号申请邮箱:${TO_MAIL},账号开通链接为(12小时内有效):
74+
</span>
75+
</div>
76+
<hr style="
77+
display: flex;
78+
position: relative;
79+
border: 1px dashed #ef859d2e;
80+
box-sizing: content-box;
81+
height: 0px;
82+
overflow: visible;
83+
width: 100%;
84+
">
85+
<div class="text-wrapper_4 flex-col justify-between" style="
86+
display: flex;
87+
flex-direction: column;
88+
margin-left: 30px;
89+
">
90+
<hr>
91+
</h1>
92+
<p style="margin-top: 20px;">
93+
请点击以下按钮进行开通:
94+
<span class="text_4" style="
95+
margin-top: 6px;
96+
margin-right: 22px;
97+
font-size: 16px;
98+
font-family: PingFangSC-Regular, PingFang SC;
99+
font-weight: 400;
100+
color: #000000;
101+
line-height: 22px;
102+
"></span>
103+
</div>
104+
105+
<a class="text-wrapper_2 flex-col" style="
106+
min-width: 106px;
107+
height: 38px;
108+
background: #ef859d38;
109+
border-radius: 32px;
110+
display: flex;
111+
align-items: center;
112+
justify-content: center;
113+
text-decoration: none;
114+
margin: auto;
115+
margin-top: 32px;
116+
" href="${VERIFY_URL}">
117+
<span class="text_5" style="
118+
color: #DB214B;
119+
">点我开通</span>
120+
</a>
121+
</div>
122+
<div class="text-group_6 flex-col justify-between" style="
123+
display: flex;
124+
flex-direction: column;
125+
align-items: center;
126+
margin-top: 34px;
127+
">
128+
<span class="text_6" style="
129+
height: 17px;
130+
font-size: 12px;
131+
font-family: PingFangSC-Regular, PingFang SC;
132+
font-weight: 400;
133+
color: #00000045;
134+
line-height: 17px;
135+
">此邮件由服务器自动发出,直接回复无效。</span>
136+
</div>
137+
</div>
138+
</div>
139+
</body>
140+
</html>

0 commit comments

Comments
 (0)