chore: allow to log in via email address as a fallback (resolve #878)

This commit is contained in:
Ferdinand Mütsch
2025-11-27 18:06:04 +01:00
parent 69bd3df592
commit 5080344cea
6 changed files with 78 additions and 3 deletions

View File

@@ -28,6 +28,9 @@ func (m *UserServiceMock) GetUserByKey(s string, r bool) (*models.User, error) {
func (m *UserServiceMock) GetUserByEmail(s string) (*models.User, error) {
args := m.Called(s)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*models.User), args.Error(1)
}

View File

@@ -106,10 +106,13 @@ func (h *LoginHandler) PostLogin(w http.ResponseWriter, r *http.Request) {
user, err := h.userSrvc.GetUserById(login.Username)
if err != nil {
// try to get by email if given username is an email address (checked inside service)
if user, err = h.userSrvc.GetUserByEmail(login.Username); err != nil {
w.WriteHeader(http.StatusNotFound)
templates[conf.LoginTemplate].Execute(w, h.buildViewModel(r, w, false).WithError("resource not found"))
return
}
}
if !utils.ComparePassword(user.Password, login.Password, h.config.Security.PasswordSalt) {
w.WriteHeader(http.StatusUnauthorized)

View File

@@ -162,6 +162,7 @@ func (suite *LoginHandlerTestSuite) TestPostLogin_NonExistingUser() {
w := httptest.NewRecorder()
suite.UserService.On("GetUserById", "nonexisting").Return(nil, errors.New(""))
suite.UserService.On("GetUserByEmail", "nonexisting").Return(nil, errors.New(""))
suite.UserService.On("Count").Return(1, nil)
suite.Sut.PostLogin(w, r)

View File

@@ -10,6 +10,7 @@ import (
"github.com/duke-git/lancet/v2/convertor"
"github.com/duke-git/lancet/v2/datetime"
"github.com/duke-git/lancet/v2/validator"
"github.com/gofrs/uuid/v5"
"github.com/leandro-lugaresi/hub"
"github.com/patrickmn/go-cache"
@@ -127,6 +128,9 @@ func (srv *UserService) GetUserByEmail(email string) (*models.User, error) {
if email == "" {
return nil, errors.New("email must not be empty")
}
if !validator.IsEmail(email) {
return nil, errors.New("not a valid email")
}
return srv.repository.FindOne(models.User{Email: email})
}

View File

@@ -39,6 +39,38 @@ func TestUserServiceTestSuite(t *testing.T) {
suite.Run(t, new(UserServiceTestSuite))
}
func (suite *UserServiceTestSuite) TestUserService_GetByEmail_Empty() {
sut := NewUserService(suite.KeyValueService, suite.MailService, suite.ApiKeyService, suite.UserRepo)
result, err := sut.GetUserByEmail("")
suite.Nil(result)
suite.NotNil(err)
suite.Equal(err, errors.New("email must not be empty"))
}
func (suite *UserServiceTestSuite) TestUserService_GetByEmail_Invalid() {
sut := NewUserService(suite.KeyValueService, suite.MailService, suite.ApiKeyService, suite.UserRepo)
result, err := sut.GetUserByEmail("notanemailaddress")
suite.Nil(result)
suite.NotNil(err)
suite.Equal(err, errors.New("not a valid email"))
}
func (suite *UserServiceTestSuite) TestUserService_GetByEmail_Valid() {
const testEmail = "foo@bar.com"
suite.UserRepo.On("FindOne", models.User{Email: testEmail}).Return(suite.TestUser, nil)
sut := NewUserService(suite.KeyValueService, suite.MailService, suite.ApiKeyService, suite.UserRepo)
result, err := sut.GetUserByEmail(testEmail)
suite.Equal(suite.TestUser, result)
suite.Nil(err)
}
func (suite *UserServiceTestSuite) TestUserService_GetByEmptyKey_Failed() {
sut := NewUserService(suite.KeyValueService, suite.MailService, suite.ApiKeyService, suite.UserRepo)

View File

@@ -0,0 +1,32 @@
meta {
name: Login (by email)
type: http
seq: 5
}
post {
url: {{BASE_URL}}/login
body: formUrlEncoded
auth: none
}
body:form-urlencoded {
username: testuser@wakapi.dev
password: testpassword
}
assert {
res.status: eq 302
res.headers['location']: eq /summary
}
script:pre-request {
// Do not follow 3xx redirects
req.setMaxRedirects(0)
}
tests {
test("Sets cookie", function () {
expect(res.headers["set-cookie"].some(str => str.includes("wakapi_auth="))).to.be.true
});
}