From 7c011066d15a15a2428feee3b03b3bf0b9d7c79d Mon Sep 17 00:00:00 2001 From: ckt1031 <65409152+ckt1031@users.noreply.github.com> Date: Sun, 30 Jul 2023 11:45:42 +0800 Subject: [PATCH] feat: support email domain restriction --- common/constants.go | 11 ++++ controller/misc.go | 18 +++++- controller/option.go | 11 +++- model/option.go | 6 ++ web/src/components/SystemSetting.js | 86 ++++++++++++++++++++++++++--- 5 files changed, 122 insertions(+), 10 deletions(-) diff --git a/common/constants.go b/common/constants.go index c4bb6671..11a07f0b 100644 --- a/common/constants.go +++ b/common/constants.go @@ -42,6 +42,17 @@ var WeChatAuthEnabled = false var TurnstileCheckEnabled = false var RegisterEnabled = true +var EmailDomainRestrictionEnabled = false +var RestrictedEmailDomains = []string{ + "gmail.com", + "163.com", + "qq.com", + "outlook.com", + "hotmail.com", + "icloud.com", + "yahoo.com", +} + var LogConsumeEnabled = true var SMTPServer = "" diff --git a/controller/misc.go b/controller/misc.go index 958a3716..a8c98a44 100644 --- a/controller/misc.go +++ b/controller/misc.go @@ -3,10 +3,12 @@ package controller import ( "encoding/json" "fmt" - "github.com/gin-gonic/gin" "net/http" "one-api/common" "one-api/model" + "strings" + + "github.com/gin-gonic/gin" ) func GetStatus(c *gin.Context) { @@ -78,6 +80,20 @@ func SendEmailVerification(c *gin.Context) { }) return } + if common.EmailDomainRestrictionEnabled { + allowedEmailDomains := common.RestrictedEmailDomains + + // Check if email suffix is allowed + allowed := strings.Contains(strings.Join(allowedEmailDomains, ","), strings.Split(email, "@")[1]) + + if !allowed { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": "该邮箱地址不允许注册", + }) + return + } + } if model.IsEmailAlreadyTaken(email) { c.JSON(http.StatusOK, gin.H{ "success": false, diff --git a/controller/option.go b/controller/option.go index abf0d5be..a176f9dd 100644 --- a/controller/option.go +++ b/controller/option.go @@ -2,11 +2,12 @@ package controller import ( "encoding/json" - "github.com/gin-gonic/gin" "net/http" "one-api/common" "one-api/model" "strings" + + "github.com/gin-gonic/gin" ) func GetOptions(c *gin.Context) { @@ -49,6 +50,14 @@ func UpdateOption(c *gin.Context) { }) return } + case "EmailDomainRestrictionEnabled": + if option.Value == "true" && len(common.RestrictedEmailDomains) == 0 { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": "无法启用邮箱域名限制,请先填入限制的邮箱域名!", + }) + return + } case "WeChatAuthEnabled": if option.Value == "true" && common.WeChatServerAddress == "" { c.JSON(http.StatusOK, gin.H{ diff --git a/model/option.go b/model/option.go index e7bc6806..3b464f54 100644 --- a/model/option.go +++ b/model/option.go @@ -39,6 +39,8 @@ func InitOptionMap() { common.OptionMap["DisplayInCurrencyEnabled"] = strconv.FormatBool(common.DisplayInCurrencyEnabled) common.OptionMap["DisplayTokenStatEnabled"] = strconv.FormatBool(common.DisplayTokenStatEnabled) common.OptionMap["ChannelDisableThreshold"] = strconv.FormatFloat(common.ChannelDisableThreshold, 'f', -1, 64) + common.OptionMap["EmailDomainRestrictionEnabled"] = strconv.FormatBool(common.EmailDomainRestrictionEnabled) + common.OptionMap["RestrictedEmailDomains"] = strings.Join(common.RestrictedEmailDomains, ",") common.OptionMap["SMTPServer"] = "" common.OptionMap["SMTPFrom"] = "" common.OptionMap["SMTPPort"] = strconv.Itoa(common.SMTPPort) @@ -141,6 +143,8 @@ func updateOptionMap(key string, value string) (err error) { common.TurnstileCheckEnabled = boolValue case "RegisterEnabled": common.RegisterEnabled = boolValue + case "EmailDomainRestrictionEnabled": + common.EmailDomainRestrictionEnabled = boolValue case "AutomaticDisableChannelEnabled": common.AutomaticDisableChannelEnabled = boolValue case "ApproximateTokenEnabled": @@ -154,6 +158,8 @@ func updateOptionMap(key string, value string) (err error) { } } switch key { + case "RestrictedEmailDomains": + common.RestrictedEmailDomains = strings.Split(value, ",") case "SMTPServer": common.SMTPServer = value case "SMTPPort": diff --git a/web/src/components/SystemSetting.js b/web/src/components/SystemSetting.js index 658e5294..062014d1 100644 --- a/web/src/components/SystemSetting.js +++ b/web/src/components/SystemSetting.js @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; -import { Divider, Form, Grid, Header, Message } from 'semantic-ui-react'; -import { API, removeTrailingSlash, showError, verifyJSON } from '../helpers'; +import { Button, Divider, Form, Grid, Header, Input, Message } from 'semantic-ui-react'; +import { API, removeTrailingSlash, showError } from '../helpers'; const SystemSetting = () => { let [inputs, setInputs] = useState({ @@ -26,9 +26,13 @@ const SystemSetting = () => { TurnstileSiteKey: '', TurnstileSecretKey: '', RegisterEnabled: '', + EmailDomainRestrictionEnabled: '', + RestrictedEmailDomains: '' }); const [originInputs, setOriginInputs] = useState({}); let [loading, setLoading] = useState(false); + const [restrictedEmailDomains, setRestrictedEmailDomains] = useState([]); + const [restrictedDomainInput, setRestrictedDomainInput] = useState(''); const getOptions = async () => { const res = await API.get('/api/option/'); @@ -38,8 +42,15 @@ const SystemSetting = () => { data.forEach((item) => { newInputs[item.key] = item.value; }); - setInputs(newInputs); + setInputs({ + ...newInputs, + RestrictedEmailDomains: newInputs.RestrictedEmailDomains.split(',') + }); setOriginInputs(newInputs); + + setRestrictedEmailDomains(newInputs.RestrictedEmailDomains.split(',').map((item) => { + return { key: item, text: item, value: item }; + })); } else { showError(message); } @@ -58,6 +69,7 @@ const SystemSetting = () => { case 'GitHubOAuthEnabled': case 'WeChatAuthEnabled': case 'TurnstileCheckEnabled': + case 'EmailDomainRestrictionEnabled': case 'RegisterEnabled': value = inputs[key] === 'true' ? 'false' : 'true'; break; @@ -70,7 +82,12 @@ const SystemSetting = () => { }); const { success, message } = res.data; if (success) { - setInputs((inputs) => ({ ...inputs, [key]: value })); + if (key === 'RestrictedEmailDomains') { + value = value.split(','); + } + setInputs((inputs) => ({ + ...inputs, [key]: value + })); } else { showError(message); } @@ -88,7 +105,8 @@ const SystemSetting = () => { name === 'WeChatServerToken' || name === 'WeChatAccountQRCodeImageURL' || name === 'TurnstileSiteKey' || - name === 'TurnstileSecretKey' + name === 'TurnstileSecretKey' || + name === 'RestrictedEmailDomains' ) { setInputs((inputs) => ({ ...inputs, [name]: value })); } else { @@ -123,6 +141,12 @@ const SystemSetting = () => { ) { await updateOption('SMTPToken', inputs.SMTPToken); } + if ( + originInputs['RestrictedEmailDomains'] !== inputs.RestrictedEmailDomains.join(',') && + inputs.SMTPToken !== '' + ) { + await updateOption('RestrictedEmailDomains', inputs.RestrictedEmailDomains.join(',')); + } }; const submitWeChat = async () => { @@ -282,12 +306,58 @@ const SystemSetting = () => { label='SMTP 访问凭证' name='SMTPToken' onChange={handleInputChange} - type='password' - autoComplete='new-password' - value={inputs.SMTPToken} + checked={inputs.RegisterEnabled === 'true'} placeholder='敏感信息不会发送到前端显示' /> + + + + + + { + const localDomainList = inputs.RestrictedEmailDomains; + if (restrictedDomainInput !== '' && !localDomainList.includes(restrictedDomainInput)) { + setRestrictedDomainInput(''); + setInputs({ + ...inputs, + RestrictedEmailDomains: [...localDomainList, restrictedDomainInput], + }); + setRestrictedEmailDomains([...restrictedEmailDomains, { + key: restrictedDomainInput, + text: restrictedDomainInput, + value: restrictedDomainInput, + }]); + } + }}>填入 + } + placeholder='输入受限电子邮件域名' + value={restrictedDomainInput} + onChange={(e, { value }) => { + setRestrictedDomainInput(value); + }} + /> + 保存 SMTP 设置