diff --git a/backend/biz/team/handler/http/v1/user.go b/backend/biz/team/handler/http/v1/user.go index b060c836..7d79d1dd 100644 --- a/backend/biz/team/handler/http/v1/user.go +++ b/backend/biz/team/handler/http/v1/user.go @@ -97,7 +97,7 @@ func NewTeamGroupUserHandler(i *do.Injector) (*TeamGroupUserHandler, error) { // @Router /api/v1/teams/users/login [post] func (h *TeamGroupUserHandler) Login(c *web.Context, req domain.TeamLoginReq) error { ctx := c.Request().Context() - if !h.captcha.ValidateToken(ctx, req.CaptchaToken) { + if h.config.Security.CaptchaEnabled && !h.captcha.ValidateToken(ctx, req.CaptchaToken) { return errcode.ErrForbidden } diff --git a/backend/biz/team/handler/http/v1/user_captcha_test.go b/backend/biz/team/handler/http/v1/user_captcha_test.go index cfdc9fb8..577b6965 100644 --- a/backend/biz/team/handler/http/v1/user_captcha_test.go +++ b/backend/biz/team/handler/http/v1/user_captcha_test.go @@ -1,16 +1,19 @@ package v1 import ( + "bytes" "context" "errors" "io" "log/slog" + "net/http" "net/http/httptest" "testing" "github.com/GoYoko/web" "github.com/labstack/echo/v4" + "github.com/chaitin/MonkeyCode/backend/config" "github.com/chaitin/MonkeyCode/backend/domain" "github.com/chaitin/MonkeyCode/backend/errcode" "github.com/chaitin/MonkeyCode/backend/pkg/captcha" @@ -31,9 +34,10 @@ func TestTeamLoginCaptchaToggle(t *testing.T) { t.Run(tt.name, func(t *testing.T) { usecase := &teamLoginUsecaseStub{} h := &TeamGroupUserHandler{ + config: &config.Config{Security: config.Security{CaptchaEnabled: tt.enabled}}, logger: slog.New(slog.NewTextHandler(io.Discard, nil)), usecase: usecase, - captcha: captcha.NewCaptcha(tt.enabled), + captcha: captcha.NewCaptcha(), } err := h.Login(teamTestWebContext(), domain.TeamLoginReq{}) @@ -47,6 +51,38 @@ func TestTeamLoginCaptchaToggle(t *testing.T) { } } +func TestTeamLoginAcceptsEmptyCaptchaTokenWhenDisabled(t *testing.T) { + tests := []struct { + name string + body string + }{ + {name: "missing", body: `{"email":"admin@example.com","password":"password"}`}, + {name: "empty", body: `{"email":"admin@example.com","password":"password","captcha_token":""}`}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + usecase := &teamLoginUsecaseStub{} + h := &TeamGroupUserHandler{ + config: &config.Config{Security: config.Security{CaptchaEnabled: false}}, + logger: slog.New(slog.NewTextHandler(io.Discard, nil)), + usecase: usecase, + captcha: captcha.NewCaptcha(), + } + w := web.New() + w.POST("/login", web.BindHandler(h.Login)) + + req := httptest.NewRequest(http.MethodPost, "/login", bytes.NewBufferString(tt.body)) + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + w.Echo().ServeHTTP(httptest.NewRecorder(), req) + + if !usecase.called { + t.Fatal("Login usecase was not called") + } + }) + } +} + func teamTestWebContext() *web.Context { e := echo.New() req := httptest.NewRequest("POST", "/", nil) diff --git a/backend/biz/user/handler/v1/auth.go b/backend/biz/user/handler/v1/auth.go index abd6590b..fded268e 100644 --- a/backend/biz/user/handler/v1/auth.go +++ b/backend/biz/user/handler/v1/auth.go @@ -242,7 +242,7 @@ func oauthErrorCode(err error) string { // @Router /api/v1/users/password-login [post] func (h *AuthHandler) PasswordLogin(c *web.Context, req domain.TeamLoginReq) error { ctx := c.Request().Context() - if !h.captcha.ValidateToken(ctx, req.CaptchaToken) { + if h.config.Security.CaptchaEnabled && !h.captcha.ValidateToken(ctx, req.CaptchaToken) { return errcode.ErrForbidden } diff --git a/backend/biz/user/handler/v1/auth_captcha_test.go b/backend/biz/user/handler/v1/auth_captcha_test.go index 4ca301cf..4214c694 100644 --- a/backend/biz/user/handler/v1/auth_captcha_test.go +++ b/backend/biz/user/handler/v1/auth_captcha_test.go @@ -1,16 +1,19 @@ package v1 import ( + "bytes" "context" "errors" "io" "log/slog" + "net/http" "net/http/httptest" "testing" "github.com/GoYoko/web" "github.com/labstack/echo/v4" + "github.com/chaitin/MonkeyCode/backend/config" "github.com/chaitin/MonkeyCode/backend/domain" "github.com/chaitin/MonkeyCode/backend/errcode" "github.com/chaitin/MonkeyCode/backend/pkg/captcha" @@ -31,9 +34,10 @@ func TestPasswordLoginCaptchaToggle(t *testing.T) { t.Run(tt.name, func(t *testing.T) { usecase := &passwordLoginUsecaseStub{} h := &AuthHandler{ + config: &config.Config{Security: config.Security{CaptchaEnabled: tt.enabled}}, logger: slog.New(slog.NewTextHandler(io.Discard, nil)), usecase: usecase, - captcha: captcha.NewCaptcha(tt.enabled), + captcha: captcha.NewCaptcha(), } err := h.PasswordLogin(testWebContext(), domain.TeamLoginReq{}) @@ -47,6 +51,38 @@ func TestPasswordLoginCaptchaToggle(t *testing.T) { } } +func TestPasswordLoginAcceptsEmptyCaptchaTokenWhenDisabled(t *testing.T) { + tests := []struct { + name string + body string + }{ + {name: "missing", body: `{"email":"user@example.com","password":"password"}`}, + {name: "empty", body: `{"email":"user@example.com","password":"password","captcha_token":""}`}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + usecase := &passwordLoginUsecaseStub{} + h := &AuthHandler{ + config: &config.Config{Security: config.Security{CaptchaEnabled: false}}, + logger: slog.New(slog.NewTextHandler(io.Discard, nil)), + usecase: usecase, + captcha: captcha.NewCaptcha(), + } + w := web.New() + w.POST("/login", web.BindHandler(h.PasswordLogin)) + + req := httptest.NewRequest(http.MethodPost, "/login", bytes.NewBufferString(tt.body)) + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + w.Echo().ServeHTTP(httptest.NewRecorder(), req) + + if !usecase.called { + t.Fatal("PasswordLogin usecase was not called") + } + }) + } +} + func TestResetPasswordCaptchaToggle(t *testing.T) { tests := []struct { name string