add verification code for login and register page

This commit is contained in:
RockYang 2024-08-13 14:55:47 +08:00
parent 7fe50788a5
commit 0ea4a29152
14 changed files with 207 additions and 55 deletions

View File

@ -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"` // 是否启用验证码
}

View File

@ -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 {

View File

@ -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

View File

@ -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)

View File

@ -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 {

View File

@ -55,7 +55,7 @@
还没有账号
<el-tag @click="login = false">注册</el-tag>
</span>
<el-button type="info" class="forget" size="small" @click="login = false">忘记密码</el-button>
<el-button type="info" class="forget" size="small" @click="showResetPass = true">忘记密码</el-button>
</el-col>
</el-row>
@ -226,6 +226,10 @@
</el-row>
</div>
</div>
<captcha v-if="enableVerify" @success="submit" ref="captchaRef"/>
<reset-pass @hide="showResetPass = false" :show="showResetPass"/>
</el-dialog>
</template>
@ -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: () => {

View File

@ -1,10 +1,10 @@
<template>
<el-container class="send-verify-code">
<el-button type="primary" class="send-btn" :size="props.size" :disabled="!canSend" @click="showCaptcha" plain>
<el-button type="primary" class="send-btn" :size="props.size" :disabled="!canSend" @click="sendMsg" plain>
{{ btnText }}
</el-button>
<captcha @success="sendMsg" ref="captchaRef"/>
<captcha @success="doSendMsg" ref="captchaRef"/>
</el-container>
</template>
@ -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
}

View File

@ -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)
})
})
}

View File

@ -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)
}

View File

@ -89,7 +89,8 @@ onMounted(() => {
const getRoles = () => {
checkSession().then(user => {
roles.value = user.chat_roles
}).catch(() => {
}).catch(e => {
console.log(e.message)
})
}

View File

@ -72,7 +72,6 @@
<div v-else>
<el-button size="small" color="#21aa93" @click="store.setShowLoginDialog(true)" round>登录</el-button>
<el-button size="small" @click="router.push('/register')" round>注册</el-button>
</div>
</div>
</div>
@ -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
});

View File

@ -55,6 +55,8 @@
</div>
<reset-pass @hide="showResetPass = false" :show="showResetPass"/>
<captcha v-if="enableVerify" @success="doLogin" ref="captchaRef"/>
<footer-bar/>
</div>
@ -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')

View File

@ -160,6 +160,8 @@
</el-result>
</div>
<captcha v-if="enableVerify" @success="doSubmitRegister" ref="captchaRef"/>
<footer class="footer" v-if="!licenseConfig.de_copy">
<footer-bar/>
</footer>
@ -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"),

View File

@ -37,6 +37,8 @@
</div>
</div>
<captcha v-if="enableVerify" @success="doLogin" ref="captchaRef"/>
<footer class="footer">
<footer-bar/>
</footer>
@ -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) => {