diff --git a/api/core/types/config.go b/api/core/types/config.go index 10f16f62..07d19ff1 100644 --- a/api/core/types/config.go +++ b/api/core/types/config.go @@ -161,10 +161,10 @@ type SystemConfig struct { SdNegPrompt string `json:"sd_neg_prompt"` // SD 默认反向提示词 MjMode string `json:"mj_mode"` // midjourney 默认的API模式,relax, fast, turbo - IndexBgURL string `json:"index_bg_url"` // 前端首页背景图片 - IndexNavs []int `json:"index_navs"` // 首页显示的导航菜单 - Copyright string `json:"copyright"` // 版权信息 - MarkMapText string `json:"mark_map_text,omitempty"` // 思维导入的默认文本 + IndexBgURL string `json:"index_bg_url"` // 前端首页背景图片 + IndexNavs []int `json:"index_navs"` // 首页显示的导航菜单 + Copyright string `json:"copyright"` // 版权信息 + MarkMapText string `json:"mark_map_text"` // 思维导入的默认文本 - EnabledVerify bool `json:"enabled_verify,omitempty"` // 是否启用验证码 + EnabledVerify bool `json:"enabled_verify"` // 是否启用验证码 } diff --git a/api/handler/admin/admin_handler.go b/api/handler/admin/admin_handler.go index eaaaf8fd..f4677723 100644 --- a/api/handler/admin/admin_handler.go +++ b/api/handler/admin/admin_handler.go @@ -14,6 +14,7 @@ import ( "geekai/core/types" "geekai/handler" logger2 "geekai/logger" + "geekai/service" "geekai/store/model" "geekai/store/vo" "geekai/utils" @@ -28,33 +29,49 @@ import ( var logger = logger2.GetLogger() -// Manager 管理员 -type Manager struct { - Username string `json:"username"` - Password string `json:"password"` - Captcha string `json:"captcha"` // 验证码 - CaptchaId string `json:"captcha_id"` // 验证码id -} - const SuperManagerID = 1 type ManagerHandler struct { handler.BaseHandler - redis *redis.Client + redis *redis.Client + captcha *service.CaptchaService } -func NewAdminHandler(app *core.AppServer, db *gorm.DB, client *redis.Client) *ManagerHandler { - return &ManagerHandler{BaseHandler: handler.BaseHandler{DB: db, App: app}, redis: client} +func NewAdminHandler(app *core.AppServer, db *gorm.DB, client *redis.Client, captcha *service.CaptchaService) *ManagerHandler { + return &ManagerHandler{ + BaseHandler: handler.BaseHandler{DB: db, App: app}, + redis: client, + captcha: captcha, + } } // Login 登录 func (h *ManagerHandler) Login(c *gin.Context) { - var data Manager + var data struct { + Username string `json:"username"` + Password string `json:"password"` + Key string `json:"key,omitempty"` + Dots string `json:"dots,omitempty"` + X int `json:"x,omitempty"` + } if err := c.ShouldBindJSON(&data); err != nil { resp.ERROR(c, types.InvalidArgs) return } + if h.App.SysConfig.EnabledVerify { + var check bool + if data.X != 0 { + check = h.captcha.SlideCheck(data) + } else { + check = h.captcha.Check(data) + } + if !check { + resp.ERROR(c, "请先完人机验证") + return + } + } + var manager model.AdminUser res := h.DB.Model(&model.AdminUser{}).Where("username = ?", data.Username).First(&manager) if res.Error != nil { diff --git a/api/handler/admin/user_handler.go b/api/handler/admin/user_handler.go index 07b93700..6d713f53 100644 --- a/api/handler/admin/user_handler.go +++ b/api/handler/admin/user_handler.go @@ -49,7 +49,7 @@ func (h *UserHandler) List(c *gin.Context) { } session.Model(&model.User{}).Count(&total) - res := session.Offset(offset).Limit(pageSize).Find(&items) + res := session.Offset(offset).Limit(pageSize).Order("id DESC").Find(&items) if res.Error == nil { for _, item := range items { var user vo.User diff --git a/api/handler/sms_handler.go b/api/handler/sms_handler.go index 680f73e1..b64962c3 100644 --- a/api/handler/sms_handler.go +++ b/api/handler/sms_handler.go @@ -56,15 +56,17 @@ func (h *SmsHandler) SendCode(c *gin.Context) { resp.ERROR(c, types.InvalidArgs) return } - var check bool - if data.X != 0 { - check = h.captcha.SlideCheck(data) - } else { - check = h.captcha.Check(data) - } - if !check { - resp.ERROR(c, "验证码错误,请先完人机验证") - return + if h.App.SysConfig.EnabledVerify { + var check bool + if data.X != 0 { + check = h.captcha.SlideCheck(data) + } else { + check = h.captcha.Check(data) + } + if !check { + resp.ERROR(c, "请先完人机验证") + return + } } code := utils.RandomNumber(6) diff --git a/api/handler/user_handler.go b/api/handler/user_handler.go index db39216a..25127eda 100644 --- a/api/handler/user_handler.go +++ b/api/handler/user_handler.go @@ -33,6 +33,7 @@ type UserHandler struct { searcher *xdb.Searcher redis *redis.Client licenseService *service.LicenseService + captcha *service.CaptchaService } func NewUserHandler( @@ -40,11 +41,13 @@ func NewUserHandler( db *gorm.DB, searcher *xdb.Searcher, client *redis.Client, + captcha *service.CaptchaService, licenseService *service.LicenseService) *UserHandler { return &UserHandler{ BaseHandler: BaseHandler{DB: db, App: app}, searcher: searcher, redis: client, + captcha: captcha, licenseService: licenseService, } } @@ -58,6 +61,9 @@ func (h *UserHandler) Register(c *gin.Context) { Password string `json:"password"` Code string `json:"code"` InviteCode string `json:"invite_code"` + Key string `json:"key,omitempty"` + Dots string `json:"dots,omitempty"` + X int `json:"x,omitempty"` } if err := c.ShouldBindJSON(&data); err != nil { resp.ERROR(c, types.InvalidArgs) @@ -193,11 +199,28 @@ func (h *UserHandler) Login(c *gin.Context) { var data struct { Username string `json:"username"` Password string `json:"password"` + Key string `json:"key,omitempty"` + Dots string `json:"dots,omitempty"` + X int `json:"x,omitempty"` } if err := c.ShouldBindJSON(&data); err != nil { resp.ERROR(c, types.InvalidArgs) return } + + if h.App.SysConfig.EnabledVerify { + var check bool + if data.X != 0 { + check = h.captcha.SlideCheck(data) + } else { + check = h.captcha.Check(data) + } + if !check { + resp.ERROR(c, "请先完人机验证") + return + } + } + var user model.User res := h.DB.Where("username = ?", data.Username).First(&user) if res.Error != nil { diff --git a/web/src/components/LoginDialog.vue b/web/src/components/LoginDialog.vue index 8fe04551..b6cdd66e 100644 --- a/web/src/components/LoginDialog.vue +++ b/web/src/components/LoginDialog.vue @@ -55,7 +55,7 @@ 还没有账号? 注册 - 忘记密码? + 忘记密码? @@ -226,6 +226,10 @@ + + + + @@ -239,12 +243,14 @@ import {Checked, Close, Iphone, Lock, Message} from "@element-plus/icons-vue"; import SendMsg from "@/components/SendMsg.vue"; import {arrayContains} from "@/utils/libs"; import {getSystemInfo} from "@/store/cache"; +import Captcha from "@/components/Captcha.vue"; +import ResetPass from "@/components/ResetPass.vue"; // eslint-disable-next-line no-undef const props = defineProps({ show: Boolean, }); -const showDialog = ref(true) +const showDialog = ref(false) watch(() => props.show, (newValue) => { showDialog.value = newValue }) @@ -260,19 +266,23 @@ const data = ref({ const enableMobile = ref(false) const enableEmail = ref(false) const enableUser = ref(false) -const enableRegister = ref(false) +const enableRegister = ref(true) const wechatLoginURL = ref('') const activeName = ref("") const wxImg = ref("/images/wx.png") +const captchaRef = ref(null) // eslint-disable-next-line no-undef const emits = defineEmits(['hide', 'success']); +const action = ref("login") +const enableVerify = ref(false) +const showResetPass = ref(false) onMounted(() => { const returnURL = `${location.protocol}//${location.host}/login/callback` httpGet("/api/user/clogin?return_url="+returnURL).then(res => { wechatLoginURL.value = res.data.url }).catch(e => { - console.error(e) + console.log(e.message) }) getSystemInfo().then(res => { @@ -296,12 +306,21 @@ onMounted(() => { if (res.data['wechat_card_url'] !== '') { wxImg.value = res.data['wechat_card_url'] } + enableVerify.value = res.data['enabled_verify'] } }).catch(e => { ElMessage.error("获取系统配置失败:" + e.message) }) }) +const submit = (verifyData) => { + if (action.value === "login") { + doLogin(verifyData) + } else if (action.value === "register") { + doRegister(verifyData) + } +} + // 登录操作 const submitLogin = () => { if (data.value.username === '') { @@ -310,7 +329,18 @@ const submitLogin = () => { if (data.value.password === '') { return ElMessage.error('请输入密码'); } + if (enableVerify.value) { + captchaRef.value.loadCaptcha() + action.value = "login" + } else { + doLogin({}) + } +} +const doLogin = (verifyData) => { + data.value.key = verifyData.key + data.value.dots = verifyData.dots + data.value.x = verifyData.x httpPost('/api/user/login', data.value).then((res) => { setUserToken(res.data.token) ElMessage.success("登录成功!") @@ -345,9 +375,21 @@ const submitRegister = () => { if ((activeName.value === 'mobile' || activeName.value === 'email') && data.value.code === '') { return ElMessage.error('请输入验证码'); } + if (enableVerify.value) { + captchaRef.value.loadCaptcha() + action.value = "register" + } else { + doRegister({}) + } +} + +const doRegister = (verifyData) => { + data.value.key = verifyData.key + data.value.dots = verifyData.dots + data.value.x = verifyData.x data.value.reg_way = activeName.value httpPost('/api/user/register', data.value).then((res) => { - setUserToken(res.data) + setUserToken(res.data.token) ElMessage.success({ "message": "注册成功!", onClose: () => { diff --git a/web/src/components/SendMsg.vue b/web/src/components/SendMsg.vue index 72a30052..f39714a0 100644 --- a/web/src/components/SendMsg.vue +++ b/web/src/components/SendMsg.vue @@ -1,10 +1,10 @@ @@ -15,6 +15,7 @@ import {validateEmail, validateMobile} from "@/utils/validate"; import {httpPost} from "@/utils/http"; import {showMessageError, showMessageOK} from "@/utils/dialog"; import Captcha from "@/components/Captcha.vue"; +import {getSystemInfo} from "@/store/cache"; // eslint-disable-next-line no-undef const props = defineProps({ @@ -24,15 +25,24 @@ const props = defineProps({ const btnText = ref('发送验证码') const canSend = ref(true) const captchaRef = ref(null) +const enableVerify = ref(false) -const showCaptcha = () => { +getSystemInfo().then(res => { + enableVerify.value = res.data['enabled_verify'] +}) + +const sendMsg = () => { if (!validateMobile(props.receiver) && !validateEmail(props.receiver)) { return showMessageError("请输入合法的手机号/邮箱地址") } - captchaRef.value.loadCaptcha() + if (enableVerify.value) { + captchaRef.value.loadCaptcha() + } else { + doSendMsg({}) + } } -const sendMsg = (data) => { +const doSendMsg = (data) => { if (!canSend.value) { return } diff --git a/web/src/store/cache.js b/web/src/store/cache.js index 04409f60..fdb3284a 100644 --- a/web/src/store/cache.js +++ b/web/src/store/cache.js @@ -1,6 +1,5 @@ import {httpGet} from "@/utils/http"; import Storage from "good-storage"; -import {showMessageError} from "@/utils/dialog"; const userDataKey = "USER_INFO_CACHE_KEY" const adminDataKey = "ADMIN_INFO_CACHE_KEY" @@ -16,12 +15,12 @@ export function checkSession() { httpGet('/api/user/session').then(res => { item.data = res.data // cache expires after 10 secs - item.expire = Date.now() + 1000 * 10 + item.expire = Date.now() + 1000 * 60 * 5 Storage.set(userDataKey, item) resolve(item.data) - }).catch(err => { + }).catch(e => { Storage.remove(userDataKey) - reject(err) + reject(e) }) }) } @@ -38,11 +37,12 @@ export function checkAdminSession() { return new Promise((resolve, reject) => { httpGet('/api/admin/session').then(res => { item.data = res.data - item.expire = Date.now() + 1000 * 10 + item.expire = Date.now() + 1000 * 60 * 5 Storage.set(adminDataKey, item) resolve(item.data) - }).catch(err => { - reject(err) + }).catch(e => { + Storage.remove(adminDataKey) + reject(e) }) }) } @@ -59,11 +59,11 @@ export function getSystemInfo() { return new Promise((resolve, reject) => { httpGet('/api/config/get?key=system').then(res => { item.data = res - item.expire = Date.now() + 1000 * 10 + item.expire = Date.now() + 1000 * 60 * 10 Storage.set(systemInfoKey, item) resolve(item.data) }).catch(err => { - reject(err) + resolve(err) }) }) } @@ -77,11 +77,11 @@ export function getLicenseInfo() { return new Promise((resolve, reject) => { httpGet('/api/config/license').then(res => { item.data = res - item.expire = Date.now() + 1000 * 10 + item.expire = Date.now() + 1000 * 60 * 10 Storage.set(licenseInfoKey, item) resolve(item.data) }).catch(err => { - reject(err) + resolve(err) }) }) } \ No newline at end of file diff --git a/web/src/store/session.js b/web/src/store/session.js index 2f97602f..3f023555 100644 --- a/web/src/store/session.js +++ b/web/src/store/session.js @@ -1,6 +1,6 @@ import {randString} from "@/utils/libs"; import Storage from "good-storage"; -import {removeAdminInfo, removeUserInfo} from "@/store/cache"; +import {checkAdminSession, checkSession, removeAdminInfo, removeUserInfo} from "@/store/cache"; /** * storage handler @@ -18,6 +18,7 @@ export function getUserToken() { } export function setUserToken(token) { + // 刷新 session 缓存 Storage.set(UserTokenKey, token) } diff --git a/web/src/views/ChatApps.vue b/web/src/views/ChatApps.vue index d0e589dc..48193f8e 100644 --- a/web/src/views/ChatApps.vue +++ b/web/src/views/ChatApps.vue @@ -89,7 +89,8 @@ onMounted(() => { const getRoles = () => { checkSession().then(user => { roles.value = user.chat_roles - }).catch(() => { + }).catch(e => { + console.log(e.message) }) } diff --git a/web/src/views/Home.vue b/web/src/views/Home.vue index 298721cd..cdd49a69 100644 --- a/web/src/views/Home.vue +++ b/web/src/views/Home.vue @@ -72,7 +72,6 @@
登录 - 注册
@@ -161,7 +160,7 @@ const docsURL = ref(process.env.VUE_APP_DOCS_URL) const gitURL = ref(process.env.VUE_APP_GIT_URL) const store = useSharedStore(); -const show = ref(true) +const show = ref(false) watch(() => store.showLoginDialog, (newValue) => { show.value = newValue }); diff --git a/web/src/views/Login.vue b/web/src/views/Login.vue index 09840ef2..05b443e9 100644 --- a/web/src/views/Login.vue +++ b/web/src/views/Login.vue @@ -55,6 +55,8 @@ + + @@ -73,6 +75,7 @@ import {checkSession, getLicenseInfo, getSystemInfo} from "@/store/cache"; import {setUserToken} from "@/store/session"; import ResetPass from "@/components/ResetPass.vue"; import {showMessageError} from "@/utils/dialog"; +import Captcha from "@/components/Captcha.vue"; const router = useRouter(); const title = ref('Geek-AI'); @@ -82,12 +85,15 @@ const showResetPass = ref(false) const logo = ref("") const licenseConfig = ref({}) const wechatLoginURL = ref('') +const enableVerify = ref(false) +const captchaRef = ref(null) onMounted(() => { // 获取系统配置 getSystemInfo().then(res => { logo.value = res.data.logo title.value = res.data.title + enableVerify.value = res.data['enabled_verify'] }).catch(e => { showMessageError("获取系统配置失败:" + e.message) }) @@ -129,7 +135,21 @@ const login = function () { return showMessageError('请输入密码'); } - httpPost('/api/user/login', {username: username.value.trim(), password: password.value.trim()}).then((res) => { + if (enableVerify.value) { + captchaRef.value.loadCaptcha() + } else { + doLogin({}) + } +} + +const doLogin = (verifyData) => { + httpPost('/api/user/login', { + username: username.value.trim(), + password: password.value.trim(), + key: verifyData.key, + dots: verifyData.dots, + x: verifyData.x + }).then((res) => { setUserToken(res.data.token) if (isMobile()) { router.push('/mobile') diff --git a/web/src/views/Register.vue b/web/src/views/Register.vue index f080b568..979b7a7f 100644 --- a/web/src/views/Register.vue +++ b/web/src/views/Register.vue @@ -160,6 +160,8 @@ + +
@@ -182,6 +184,7 @@ import {setUserToken} from "@/store/session"; import {validateEmail, validateMobile} from "@/utils/validate"; import {showMessageError, showMessageOK} from "@/utils/dialog"; import {getLicenseInfo, getSystemInfo} from "@/store/cache"; +import Captcha from "@/components/Captcha.vue"; const router = useRouter(); const title = ref(''); @@ -201,6 +204,8 @@ const enableRegister = ref(true) const activeName = ref("mobile") const wxImg = ref("/images/wx.png") const licenseConfig = ref({}) +const enableVerify = ref(false) +const captchaRef = ref(null) getSystemInfo().then(res => { if (res.data) { @@ -222,6 +227,7 @@ getSystemInfo().then(res => { if (res.data['wechat_card_url'] !== '') { wxImg.value = res.data['wechat_card_url'] } + enableVerify.value = res.data['enabled_verify'] } }).catch(e => { ElMessage.error("获取系统配置失败:" + e.message) @@ -257,9 +263,21 @@ const submitRegister = () => { if ((activeName.value === 'mobile' || activeName.value === 'email') && data.value.code === '') { return showMessageError('请输入验证码'); } + + if (enableVerify.value) { + captchaRef.value.loadCaptcha() + } else { + doSubmitRegister({}) + } +} + +const doSubmitRegister = (verifyData) => { + data.value.key = verifyData.key + data.value.dots = verifyData.dots + data.value.x = verifyData.x data.value.reg_way = activeName.value httpPost('/api/user/register', data.value).then((res) => { - setUserToken(res.data) + setUserToken(res.data.token) showMessageOK({ "message": "注册成功,即将跳转到对话主界面...", onClose: () => router.push("/chat"), diff --git a/web/src/views/admin/Login.vue b/web/src/views/admin/Login.vue index 37d17458..4c326c93 100644 --- a/web/src/views/admin/Login.vue +++ b/web/src/views/admin/Login.vue @@ -37,6 +37,8 @@ + + @@ -54,12 +56,15 @@ import {useRouter} from "vue-router"; import FooterBar from "@/components/FooterBar.vue"; import {setAdminToken} from "@/store/session"; import {checkAdminSession, getSystemInfo} from "@/store/cache"; +import Captcha from "@/components/Captcha.vue"; const router = useRouter(); const title = ref('Geek-AI Console'); const username = ref(process.env.VUE_APP_ADMIN_USER); const password = ref(process.env.VUE_APP_ADMIN_PASS); const logo = ref("") +const enableVerify = ref(false) +const captchaRef = ref(null) checkAdminSession().then(() => { router.push("/admin") @@ -70,6 +75,7 @@ checkAdminSession().then(() => { getSystemInfo().then(res => { title.value = res.data.admin_title logo.value = res.data.logo + enableVerify.value = res.data['enabled_verify'] }).catch(e => { ElMessage.error("加载系统配置失败: " + e.message) }) @@ -87,8 +93,21 @@ const login = function () { if (password.value === '') { return ElMessage.error('请输入密码'); } + if (enableVerify.value) { + captchaRef.value.loadCaptcha() + } else { + doLogin({}) + } +} - httpPost('/api/admin/login', {username: username.value.trim(), password: password.value.trim()}).then(res => { +const doLogin = function (verifyData) { + httpPost('/api/admin/login', { + username: username.value.trim(), + password: password.value.trim(), + key: verifyData.key, + dots: verifyData.dots, + x: verifyData.x + }).then(res => { setAdminToken(res.data.token) router.push("/admin") }).catch((e) => {