fix: 恢复 Sense 免验证码登录 (#97)

This commit is contained in:
QiuSW
2026-08-15 18:02:10 +08:00
parent 40409707cc
commit 0bbdb27f6d
8 changed files with 58 additions and 123 deletions
+1 -11
View File
@@ -7,9 +7,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/go-admin-team/go-admin-core/sdk/api"
"github.com/go-admin-team/go-admin-core/sdk/config"
"github.com/go-admin-team/go-admin-core/sdk/pkg"
"github.com/go-admin-team/go-admin-core/sdk/pkg/captcha"
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
"github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user"
"github.com/go-admin-team/go-admin-core/sdk/pkg/response"
@@ -82,21 +80,13 @@ func Authenticator(c *gin.Context) (interface{}, error) {
return nil, jwt.ErrMissingLoginValues
}
if config.ApplicationConfig.Mode != "dev" {
if !captcha.Verify(loginVals.UUID, loginVals.Code, true) {
username = loginVals.Username
msg = "验证码错误"
status = "1"
return nil, jwt.ErrInvalidVerificationode
}
}
sysUser, role, e := loginVals.GetUser(db)
if e == nil {
username = loginVals.Username
return map[string]interface{}{"user": sysUser, "role": role}, nil
} else {
username = loginVals.Username
msg = "登录失败"
status = "1"
log.Warnf("%s login failed!", loginVals.Username)
@@ -9,8 +9,6 @@ import (
type Login struct {
Username string `form:"UserName" json:"username" binding:"required"`
Password string `form:"Password" json:"password" binding:"required"`
Code string `form:"Code" json:"code" binding:"required"`
UUID string `form:"UUID" json:"uuid" binding:"required"`
}
func (u *Login) GetUser(tx *gorm.DB) (user SysUser, role SysRole, err error) {
@@ -0,0 +1,35 @@
package handler
import (
"net/http/httptest"
"reflect"
"strings"
"testing"
"github.com/gin-gonic/gin"
)
func TestLoginAcceptsCredentialsWithoutCaptcha(t *testing.T) {
gin.SetMode(gin.TestMode)
ctx, _ := gin.CreateTestContext(httptest.NewRecorder())
ctx.Request = httptest.NewRequest("POST", "/api/v1/login", strings.NewReader(`{"username":"operator","password":"valid-password"}`))
ctx.Request.Header.Set("Content-Type", "application/json")
var login Login
if err := ctx.ShouldBindJSON(&login); err != nil {
t.Fatalf("bind credentials-only login: %v", err)
}
if login.Username != "operator" || login.Password != "valid-password" {
t.Fatalf("unexpected login payload: username=%q", login.Username)
}
typeOfLogin := reflect.TypeOf(login)
if typeOfLogin.NumField() != 2 {
t.Fatalf("login payload must only expose username and password, got %d fields", typeOfLogin.NumField())
}
for _, removed := range []string{"Code", "UUID"} {
if _, ok := typeOfLogin.FieldByName(removed); ok {
t.Fatalf("captcha field %s must not be part of the login payload", removed)
}
}
}
+1 -9
View File
@@ -3798,23 +3798,15 @@ const docTemplateadmin = `{
"handler.Login": {
"type": "object",
"required": [
"code",
"password",
"username",
"uuid"
"username"
],
"properties": {
"code": {
"type": "string"
},
"password": {
"type": "string"
},
"username": {
"type": "string"
},
"uuid": {
"type": "string"
}
}
},
+2 -10
View File
@@ -3789,23 +3789,15 @@
"handler.Login": {
"type": "object",
"required": [
"code",
"password",
"username",
"uuid"
"username"
],
"properties": {
"code": {
"type": "string"
},
"password": {
"type": "string"
},
"username": {
"type": "string"
},
"uuid": {
"type": "string"
}
}
},
@@ -4345,4 +4337,4 @@
"in": "header"
}
}
}
}
@@ -685,19 +685,13 @@ definitions:
type: object
handler.Login:
properties:
code:
type: string
password:
type: string
username:
type: string
uuid:
type: string
required:
- code
- password
- username
- uuid
type: object
models.SysApi:
properties:
+4 -85
View File
@@ -102,29 +102,6 @@
</el-input>
</el-form-item>
<el-form-item label="验证码" prop="code">
<div class="captcha-row">
<el-input
v-model="loginForm.code"
placeholder="请输入验证码"
name="code"
type="text"
tabindex="3"
maxlength="5"
autocomplete="off"
size="large"
:prefix-icon="Key"
@keyup.enter="handleLogin"
/>
<div class="captcha-wrap" title="点击刷新" @click="getCode">
<img v-if="codeUrl" :src="codeUrl" class="captcha-img" alt="验证码">
<div v-else class="captcha-placeholder">
<el-icon class="is-loading"><Loading /></el-icon>
</div>
</div>
</div>
</el-form-item>
<el-button
:loading="loading"
type="primary"
@@ -142,27 +119,22 @@
</template>
<script>
import { getCodeImg } from '@/api/login'
import { User, Lock, Key, View, Hide, Monitor, Loading } from '@element-plus/icons-vue'
import { User, Lock, View, Hide, Monitor } from '@element-plus/icons-vue'
export default {
name: 'LoginPage',
setup() {
return { User, Lock, Key, View, Hide, Monitor, Loading }
return { User, Lock, View, Hide, Monitor }
},
data() {
return {
codeUrl: '',
loginForm: {
username: '',
password: '',
code: '',
uuid: ''
password: ''
},
loginRules: {
username: [{ required: true, trigger: 'blur', message: '用户名不能为空' }],
password: [{ required: true, trigger: 'blur', message: '密码不能为空' }],
code: [{ required: true, trigger: 'change', message: '验证码不能为空' }]
password: [{ required: true, trigger: 'blur', message: '密码不能为空' }]
},
passwordType: 'password',
capsTooltip: false,
@@ -185,7 +157,6 @@ export default {
}
},
created() {
this.getCode()
this.getSystemSetting()
},
mounted() {
@@ -202,15 +173,6 @@ export default {
document.title = ret.sys_app_name
})
},
getCode() {
this.codeUrl = ''
getCodeImg().then((res) => {
if (res !== undefined) {
this.codeUrl = res.data
this.loginForm.uuid = res.id
}
})
},
checkCapslock({ shiftKey, key } = {}) {
if (key && key.length === 1) {
if ((shiftKey && key >= 'a' && key <= 'z') || (!shiftKey && key >= 'A' && key <= 'Z')) {
@@ -238,7 +200,6 @@ export default {
})
.catch(() => {
this.loading = false
this.getCode()
})
}
})
@@ -562,48 +523,6 @@ export default {
}
}
/* ── 验证码 ── */
.captcha-row {
display: flex;
gap: 10px;
align-items: center;
.el-input {
flex: 1;
}
}
.captcha-wrap {
width: 110px;
height: 40px;
flex-shrink: 0;
border-radius: 8px;
border: 1px solid #e5e7eb;
overflow: hidden;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
background: #f9fafb;
transition: border-color 0.2s;
&:hover {
border-color: #3b82f6;
}
}
.captcha-img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.captcha-placeholder {
color: #c1c7d0;
font-size: 18px;
}
/* ── 登录按钮 ── */
.submit-btn {
width: 100%;
@@ -0,0 +1,15 @@
import LoginPage from '@/views/login/index.vue'
describe('Sense login page', () => {
it('uses username and password without a captcha challenge', () => {
const state = LoginPage.data()
const getSystemSetting = jest.fn()
LoginPage.created.call({ getSystemSetting })
expect(Object.keys(state.loginForm)).toEqual(['username', 'password'])
expect(Object.keys(state.loginRules)).toEqual(['username', 'password'])
expect(LoginPage.methods.getCode).toBeUndefined()
expect(getSystemSetting).toHaveBeenCalledTimes(1)
})
})