Skip to content

Commit b1ab111

Browse files
committed
perf: optimize login API logic
1 parent 4ac490c commit b1ab111

File tree

12 files changed

+158
-28
lines changed

12 files changed

+158
-28
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,4 @@ build_backend_on_darwin:
3131

3232
build_all: build_frontend build_backend_on_linux
3333

34-
build_on_local: clean_assets build_frontend build_backend_on_darwin upx_bin
34+
build_on_local: clean_assets build_frontend build_backend_on_darwin

backend/app/api/v1/auth.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ package v1
22

33
import (
44
"encoding/base64"
5+
56
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
67
"github.com/1Panel-dev/1Panel/backend/app/dto"
78
"github.com/1Panel-dev/1Panel/backend/app/model"
89
"github.com/1Panel-dev/1Panel/backend/constant"
910
"github.com/1Panel-dev/1Panel/backend/global"
1011
"github.com/1Panel-dev/1Panel/backend/utils/captcha"
12+
"github.com/1Panel-dev/1Panel/backend/utils/common"
1113
"github.com/gin-gonic/gin"
1214
)
1315

@@ -26,7 +28,9 @@ func (b *BaseApi) Login(c *gin.Context) {
2628
return
2729
}
2830

29-
if req.AuthMethod != "jwt" && !req.IgnoreCaptcha {
31+
ip := common.GetRealClientIP(c)
32+
needCaptcha := global.IPTracker.NeedCaptcha(ip)
33+
if needCaptcha {
3034
if err := captcha.VerifyCode(req.CaptchaID, req.Captcha); err != nil {
3135
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
3236
return
@@ -48,9 +52,11 @@ func (b *BaseApi) Login(c *gin.Context) {
4852
user, err := authService.Login(c, req, string(entrance))
4953
go saveLoginLogs(c, err)
5054
if err != nil {
55+
global.IPTracker.SetNeedCaptcha(ip)
5156
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
5257
return
5358
}
59+
global.IPTracker.Clear(ip)
5460
helper.SuccessWithData(c, user)
5561
}
5662

@@ -134,16 +140,21 @@ func (b *BaseApi) CheckIsIntl(c *gin.Context) {
134140
}
135141

136142
// @Tags Auth
137-
// @Summary Load System Language
138-
// @Success 200 {string} language
139-
// @Router /auth/language [get]
140-
func (b *BaseApi) GetLanguage(c *gin.Context) {
143+
// @Summary Load System Setting for login
144+
// @Success 200 {object} dto.LoginSetting
145+
// @Router /auth/setting [get]
146+
func (b *BaseApi) GetAuthSetting(c *gin.Context) {
141147
settingInfo, err := settingService.GetSettingInfo()
142148
if err != nil {
143149
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
144150
return
145151
}
146-
helper.SuccessWithData(c, settingInfo.Language)
152+
ip := common.GetRealClientIP(c)
153+
needCaptcha := global.IPTracker.NeedCaptcha(ip)
154+
helper.SuccessWithData(c, dto.LoginSetting{
155+
NeedCaptcha: needCaptcha,
156+
Language: settingInfo.Language,
157+
})
147158
}
148159

149160
func saveLoginLogs(c *gin.Context, err error) {

backend/app/dto/auth.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,12 @@ type MfaCredential struct {
2323
}
2424

2525
type Login struct {
26-
Name string `json:"name" validate:"required"`
27-
Password string `json:"password" validate:"required"`
28-
IgnoreCaptcha bool `json:"ignoreCaptcha"`
29-
Captcha string `json:"captcha"`
30-
CaptchaID string `json:"captchaID"`
31-
AuthMethod string `json:"authMethod" validate:"required,oneof=jwt session"`
32-
Language string `json:"language" validate:"required,oneof=zh en tw ja ko ru ms 'pt-BR'"`
26+
Name string `json:"name" validate:"required"`
27+
Password string `json:"password" validate:"required"`
28+
Captcha string `json:"captcha"`
29+
CaptchaID string `json:"captchaID"`
30+
AuthMethod string `json:"authMethod" validate:"required,oneof=jwt session"`
31+
Language string `json:"language" validate:"required,oneof=zh en tw ja ko ru ms 'pt-BR'"`
3332
}
3433

3534
type MFALogin struct {
@@ -38,3 +37,8 @@ type MFALogin struct {
3837
Code string `json:"code" validate:"required"`
3938
AuthMethod string `json:"authMethod"`
4039
}
40+
41+
type LoginSetting struct {
42+
NeedCaptcha bool `json:"needCaptcha"`
43+
Language string `json:"language"`
44+
}

backend/global/global.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package global
22

33
import (
44
"github.com/1Panel-dev/1Panel/backend/configs"
5+
"github.com/1Panel-dev/1Panel/backend/init/auth"
56
"github.com/1Panel-dev/1Panel/backend/init/cache/badger_db"
67
"github.com/1Panel-dev/1Panel/backend/init/session/psession"
78
"github.com/dgraph-io/badger/v4"
@@ -28,6 +29,8 @@ var (
2829
MonitorCronID cron.EntryID
2930
OneDriveCronID cron.EntryID
3031

32+
IPTracker *auth.IPTracker
33+
3134
I18n *i18n.Localizer
3235
I18nForCmd *i18n.Localizer
3336
)

backend/init/auth/auth.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package auth
2+
3+
import (
4+
"sync"
5+
"time"
6+
)
7+
8+
const (
9+
MaxIPCount = 100
10+
ExpireDuration = 30 * time.Minute
11+
)
12+
13+
type IPRecord struct {
14+
NeedCaptcha bool
15+
LastUpdate time.Time
16+
}
17+
18+
type IPTracker struct {
19+
records map[string]*IPRecord
20+
ipOrder []string
21+
mu sync.RWMutex
22+
}
23+
24+
func NewIPTracker() *IPTracker {
25+
return &IPTracker{
26+
records: make(map[string]*IPRecord),
27+
ipOrder: make([]string, 0),
28+
}
29+
}
30+
31+
func (t *IPTracker) NeedCaptcha(ip string) bool {
32+
t.mu.Lock()
33+
defer t.mu.Unlock()
34+
35+
record, exists := t.records[ip]
36+
if !exists {
37+
return false
38+
}
39+
40+
if time.Since(record.LastUpdate) > ExpireDuration {
41+
t.removeIPUnsafe(ip)
42+
return false
43+
}
44+
45+
return record.NeedCaptcha
46+
}
47+
48+
func (t *IPTracker) SetNeedCaptcha(ip string) {
49+
t.mu.Lock()
50+
defer t.mu.Unlock()
51+
52+
if record, exists := t.records[ip]; exists {
53+
if time.Since(record.LastUpdate) > ExpireDuration {
54+
t.removeIPUnsafe(ip)
55+
} else {
56+
record.NeedCaptcha = true
57+
record.LastUpdate = time.Now()
58+
return
59+
}
60+
}
61+
62+
if len(t.records) >= MaxIPCount {
63+
t.removeOldestUnsafe()
64+
}
65+
66+
t.records[ip] = &IPRecord{
67+
NeedCaptcha: true,
68+
LastUpdate: time.Now(),
69+
}
70+
t.ipOrder = append(t.ipOrder, ip)
71+
}
72+
73+
func (t *IPTracker) Clear(ip string) {
74+
t.mu.Lock()
75+
defer t.mu.Unlock()
76+
77+
t.removeIPUnsafe(ip)
78+
}
79+
80+
func (t *IPTracker) removeIPUnsafe(ip string) {
81+
delete(t.records, ip)
82+
83+
for i, storedIP := range t.ipOrder {
84+
if storedIP == ip {
85+
t.ipOrder = append(t.ipOrder[:i], t.ipOrder[i+1:]...)
86+
break
87+
}
88+
}
89+
}
90+
91+
func (t *IPTracker) removeOldestUnsafe() {
92+
if len(t.ipOrder) == 0 {
93+
return
94+
}
95+
96+
oldestIP := t.ipOrder[0]
97+
delete(t.records, oldestIP)
98+
t.ipOrder = t.ipOrder[1:]
99+
}

backend/router/ro_base.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ func (s *BaseRouter) InitRouter(Router *gin.RouterGroup) {
1616
baseRouter.POST("/login", baseApi.Login)
1717
baseRouter.POST("/logout", baseApi.LogOut)
1818
baseRouter.GET("/demo", baseApi.CheckIsDemo)
19-
baseRouter.GET("/language", baseApi.GetLanguage)
19+
baseRouter.GET("/setting", baseApi.GetAuthSetting)
2020
baseRouter.GET("/intl", baseApi.CheckIsIntl)
2121
}
2222
}

backend/server/server.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/1Panel-dev/1Panel/backend/i18n"
1414

1515
"github.com/1Panel-dev/1Panel/backend/init/app"
16+
"github.com/1Panel-dev/1Panel/backend/init/auth"
1617
"github.com/1Panel-dev/1Panel/backend/init/business"
1718
"github.com/1Panel-dev/1Panel/backend/init/lang"
1819

@@ -52,6 +53,7 @@ func Start() {
5253
business.Init()
5354

5455
rootRouter := router.Routers()
56+
global.IPTracker = auth.NewIPTracker()
5557

5658
tcpItem := "tcp4"
5759
if global.CONF.System.Ipv6 == "enable" {

backend/utils/captcha/captcha.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ import (
1111
var store = base64Captcha.DefaultMemStore
1212

1313
func VerifyCode(codeID string, code string) error {
14-
if codeID == "" {
15-
return constant.ErrCaptchaCode
16-
}
1714
vv := store.Get(codeID, true)
1815
vv = strings.TrimSpace(vv)
1916
code = strings.TrimSpace(code)
2017

18+
if codeID == "" || code == "" {
19+
return constant.ErrCaptchaCode
20+
}
2121
if strings.EqualFold(vv, code) {
2222
return nil
2323
}

backend/utils/common/common.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,3 +426,11 @@ func HandleIPList(content string) ([]string, error) {
426426
}
427427
return res, nil
428428
}
429+
430+
func GetRealClientIP(c *gin.Context) string {
431+
addr := c.Request.RemoteAddr
432+
if ip, _, err := net.SplitHostPort(addr); err == nil {
433+
return ip
434+
}
435+
return addr
436+
}

frontend/src/api/interface/auth.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ export namespace Login {
22
export interface ReqLoginForm {
33
name: string;
44
password: string;
5-
ignoreCaptcha: boolean;
65
captcha: string;
76
captchaID: string;
87
authMethod: string;
@@ -26,4 +25,8 @@ export namespace Login {
2625
export interface ResAuthButtons {
2726
[propName: string]: any;
2827
}
28+
export interface LoginSetting {
29+
language: string;
30+
needCaptcha: boolean;
31+
}
2932
}

0 commit comments

Comments
 (0)