Skip to content

Commit 9da479f

Browse files
author
Kerwin
committed
Merge branch 'pulls/1851624242/6'
# Conflicts: # service/src/index.ts # service/src/storage/mongo.ts
2 parents c982b61 + 3333919 commit 9da479f

File tree

19 files changed

+471
-16
lines changed

19 files changed

+471
-16
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: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,30 @@ import { auth } from './middleware/auth'
88
import { clearConfigCache, getCacheConfig, getOriginConfig } from './storage/config'
99
import type { ChatOptions, Config, MailConfig, SiteConfig, UserInfo } from './storage/model'
1010
import { Status } from './storage/model'
11-
import { clearChat, createChatRoom, createUser, deleteAllChatRooms, deleteChat, deleteChatRoom, existsChatRoom, getChat, getChatRooms, getChats, getUser, getUserById, insertChat, renameChatRoom, updateChat, updateConfig, updateUserInfo, verifyUser } from './storage/mongo'
11+
import {
12+
clearChat,
13+
createChatRoom,
14+
createUser,
15+
deleteAllChatRooms,
16+
deleteChat,
17+
deleteChatRoom,
18+
existsChatRoom,
19+
getChat,
20+
getChatRooms,
21+
getChats,
22+
getUser,
23+
getUserById,
24+
insertChat,
25+
renameChatRoom,
26+
updateChat,
27+
updateConfig,
28+
updateUserInfo,
29+
verifyUser,
30+
} from './storage/mongo'
1231
import { limiter } from './middleware/limiter'
1332
import { isEmail, isNotEmptyString } from './utils/is'
14-
import { sendTestMail, sendVerifyMail } from './utils/mail'
15-
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'
1635
import { rootAuth } from './middleware/rootAuth'
1736

1837
dotenv.config()
@@ -328,6 +347,8 @@ router.post('/user-login', async (req, res) => {
328347
|| user.password !== md5(password)) {
329348
if (user != null && user.status === Status.PreVerify)
330349
throw new Error('请去邮箱中验证 | Please verify in the mailbox')
350+
if (user != null && user.status === Status.AdminVerify)
351+
throw new Error('请等待管理员开通 | Please wait for the admin to activate')
331352
throw new Error('用户不存在或密码错误 | User does not exist or incorrect password.')
332353
}
333354
const config = await getCacheConfig()
@@ -367,8 +388,42 @@ router.post('/verify', async (req, res) => {
367388
if (!token)
368389
throw new Error('Secret key is empty')
369390
const username = await checkUserVerify(token)
370-
await verifyUser(username)
371-
res.send({ status: 'Success', message: '验证成功 | Verify successfully', data: null })
391+
const user = await getUser(username)
392+
if (user != null && user.status === Status.Normal) {
393+
res.send({ status: 'Fail', message: '邮箱已存在 | The email exists', data: null })
394+
return
395+
}
396+
const config = await getCacheConfig()
397+
let message = '验证成功 | Verify successfully'
398+
if (config.siteConfig.registerReview) {
399+
await verifyUser(username, Status.AdminVerify)
400+
await sendVerifyMailAdmin(process.env.ROOT_USER, username, await getUserVerifyUrlAdmin(username))
401+
message = '验证成功, 请等待管理员开通 | Verify successfully, Please wait for the admin to activate'
402+
}
403+
else {
404+
await verifyUser(username, Status.Normal)
405+
}
406+
res.send({ status: 'Success', message, data: null })
407+
}
408+
catch (error) {
409+
res.send({ status: 'Fail', message: error.message, data: null })
410+
}
411+
})
412+
413+
router.post('/verifyadmin', async (req, res) => {
414+
try {
415+
const { token } = req.body as { token: string }
416+
if (!token)
417+
throw new Error('Secret key is empty')
418+
const username = await checkUserVerifyAdmin(token)
419+
const user = await getUser(username)
420+
if (user != null && user.status === Status.Normal) {
421+
res.send({ status: 'Fail', message: '邮箱已开通 | The email has been opened.', data: null })
422+
return
423+
}
424+
await verifyUser(username, Status.Normal)
425+
await sendNoticeMail(username)
426+
res.send({ status: 'Success', message: '开通成功 | Activate successfully', data: null })
372427
}
373428
catch (error) {
374429
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/config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export async function getOriginConfig() {
4545
isNotEmptyString(process.env.AUTH_SECRET_KEY),
4646
process.env.AUTH_SECRET_KEY,
4747
process.env.REGISTER_ENABLED === 'true',
48+
process.env.REGISTER_REVIEW === 'true',
4849
process.env.REGISTER_MAILS,
4950
process.env.SITE_DOMAIN),
5051
new MailConfig(process.env.SMTP_HOST,
@@ -65,6 +66,8 @@ export async function getOriginConfig() {
6566
? (`${process.env.SOCKS_PROXY_USERNAME}:${process.env.SOCKS_PROXY_PASSWORD}`)
6667
: ''
6768
}
69+
if (config.siteConfig.registerReview === undefined)
70+
config.siteConfig.registerReview = process.env.REGISTER_REVIEW === 'true'
6871
}
6972
return config
7073
}

service/src/storage/model.ts

Lines changed: 2 additions & 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 {
@@ -92,6 +93,7 @@ export class SiteConfig {
9293
public loginEnabled?: boolean,
9394
public loginSalt?: string,
9495
public registerEnabled?: boolean,
96+
public registerReview?: boolean,
9597
public registerMails?: string,
9698
public siteDomain?: string,
9799
) { }

service/src/storage/mongo.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,9 @@ export async function getUserById(userId: string): Promise<UserInfo> {
151151
return await userCol.findOne({ _id: new ObjectId(userId) }) as UserInfo
152152
}
153153

154-
export async function verifyUser(email: string) {
154+
export async function verifyUser(email: string, status: Status) {
155155
email = email.toLowerCase()
156-
return await userCol.updateOne({ email }, { $set: { status: Status.Normal, verifyTime: new Date().toLocaleString() } })
156+
return await userCol.updateOne({ email }, { $set: { status, verifyTime: new Date().toLocaleString() } })
157157
}
158158

159159
export async function getConfig(): Promise<Config> {

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, verifyName: 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, verifyName)
25+
mailHtml = mailHtml.replace(/\${VERIFY_URL}/g, verifyUrl)
26+
mailHtml = mailHtml.replace(/\${SITE_TITLE}/g, config.siteConfig.siteTitle)
27+
sendMail(toMail, `${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
@@ -35,3 +35,28 @@ export function checkUserVerify(verify: string) {
3535
return username
3636
throw new Error('Verify failed')
3737
}
38+
39+
// 可以换 aes 等方式
40+
export async function getUserVerifyUrlAdmin(username: string) {
41+
const sign = getUserVerifyAdmin(username)
42+
const config = await getCacheConfig()
43+
return `${config.siteConfig.siteDomain}/#/chat/?verifytokenadmin=${sign}`
44+
}
45+
46+
function getUserVerifyAdmin(username: string) {
47+
const expired = new Date().getTime() + (12 * 60 * 60 * 1000)
48+
const sign = `${username}|${process.env.ROOT_USER}-${expired}`
49+
return `${sign}-${md5(sign)}`
50+
}
51+
52+
export function checkUserVerifyAdmin(verify: string) {
53+
const vs = verify.split('-')
54+
const sign = vs[vs.length - 1]
55+
const expired = vs[vs.length - 2]
56+
vs.splice(vs.length - 2, 2)
57+
const username = vs.join('-')
58+
// 简单点没校验有效期
59+
if (sign === md5(`${username}-${expired}`))
60+
return username.split('|')[0]
61+
throw new Error('Verify failed')
62+
}
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 target="_blank" 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)