diff --git a/REST-Tests/auth.http b/REST-Tests/auth.http index 79edc7f1f6..7da9d12d3f 100644 --- a/REST-Tests/auth.http +++ b/REST-Tests/auth.http @@ -3,7 +3,7 @@ POST http://localhost:8080/api/v1/login Content-Type: application/json { - "user": "user", + "username": "user", "password": "1234" } diff --git a/docs/errors.md b/docs/errors.md index a3570f655e..41ffdb15d2 100644 --- a/docs/errors.md +++ b/docs/errors.md @@ -11,6 +11,9 @@ This document describes the different errors Vikunja can return. | 1006 | 400 | Could not get the user id. | | 1008 | 412 | No password reset token provided. | | 1009 | 412 | Invalid password reset token. | +| 1010 | 412 | Invalid email confirm token. | +| 1011 | 412 | Wrong username or password. | +| 1012 | 412 | Email address of the user not confirmed. | | 2001 | 400 | ID cannot be empty or 0. | | 3001 | 404 | The list does not exist. | | 3004 | 403 | The user needs to have read permissions on that list to perform that action. | diff --git a/pkg/models/error.go b/pkg/models/error.go index 27f72934ee..73bdf368b8 100644 --- a/pkg/models/error.go +++ b/pkg/models/error.go @@ -197,6 +197,51 @@ func IsErrInvalidEmailConfirmToken(err error) bool { return ok } +// ErrWrongUsernameOrPassword is an error where the email was not confirmed +type ErrWrongUsernameOrPassword struct { +} + +func (err ErrWrongUsernameOrPassword) Error() string { + return fmt.Sprintf("Wrong username or password") +} + +// ErrCodeWrongUsernameOrPassword holds the unique world-error code of this error +const ErrCodeWrongUsernameOrPassword = 1011 + +// HTTPError holds the http error description +func (err ErrWrongUsernameOrPassword) HTTPError() HTTPError { + return HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeWrongUsernameOrPassword, Message: "Wrong username or password."} +} + +// IsErrWrongUsernameOrPassword checks if an error is a IsErrEmailNotConfirmed. +func IsErrWrongUsernameOrPassword(err error) bool { + _, ok := err.(ErrWrongUsernameOrPassword) + return ok +} + +// ErrEmailNotConfirmed is an error where the email was not confirmed +type ErrEmailNotConfirmed struct { + UserID int64 +} + +func (err ErrEmailNotConfirmed) Error() string { + return fmt.Sprintf("Email is not confirmed [UserID: %d]", err.UserID) +} + +// ErrCodeEmailNotConfirmed holds the unique world-error code of this error +const ErrCodeEmailNotConfirmed = 1012 + +// HTTPError holds the http error description +func (err ErrEmailNotConfirmed) HTTPError() HTTPError { + return HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeEmailNotConfirmed, Message: "Please confirm your email address."} +} + +// IsErrEmailNotConfirmed checks if an error is a IsErrEmailNotConfirmed. +func IsErrEmailNotConfirmed(err error) bool { + _, ok := err.(ErrEmailNotConfirmed) + return ok +} + // =================== // Empty things errors // =================== diff --git a/pkg/models/user.go b/pkg/models/user.go index cd944b4280..c5dae3f162 100644 --- a/pkg/models/user.go +++ b/pkg/models/user.go @@ -74,17 +74,23 @@ func GetUser(user User) (userOut User, err error) { // CheckUserCredentials checks user credentials func CheckUserCredentials(u *UserLogin) (User, error) { - // Check if the user exists user, err := GetUser(User{Username: u.Username}) if err != nil { return User{}, err } + // User is invalid if it needs to verify its email address + if !user.IsActive { + return User{}, ErrEmailNotConfirmed{UserID: user.ID} + } + // Check the users password err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(u.Password)) - if err != nil { + if err == bcrypt.ErrMismatchedHashAndPassword { + return User{}, ErrWrongUsernameOrPassword{} + } return User{}, err } diff --git a/pkg/models/user_test.go b/pkg/models/user_test.go index d99bf488b9..0ac20e2293 100644 --- a/pkg/models/user_test.go +++ b/pkg/models/user_test.go @@ -61,14 +61,22 @@ func TestCreateUser(t *testing.T) { assert.Error(t, err) assert.True(t, IsErrUserDoesNotExist(err)) - // Check the user credentials + // Check the user credentials with an unverified email user, err := CheckUserCredentials(&UserLogin{"testuu", "1234"}) + assert.Error(t, err) + assert.True(t, IsErrEmailNotConfirmed(err)) + + // Update everything and check again + _, err = x.Cols("is_active").Where("true").Update(User{IsActive: true}) + assert.NoError(t, err) + user, err = CheckUserCredentials(&UserLogin{"testuu", "1234"}) assert.NoError(t, err) assert.Equal(t, "testuu", user.Username) // Check wrong password (should also fail) _, err = CheckUserCredentials(&UserLogin{"testuu", "12345"}) assert.Error(t, err) + assert.True(t, IsErrWrongUsernameOrPassword(err)) // Check usercredentials for a nonexistent user (should fail) _, err = CheckUserCredentials(&UserLogin{"dfstestuu", "1234"}) diff --git a/pkg/routes/api/v1/login.go b/pkg/routes/api/v1/login.go index b88e5e6455..538cc6842d 100644 --- a/pkg/routes/api/v1/login.go +++ b/pkg/routes/api/v1/login.go @@ -2,6 +2,7 @@ package v1 import ( "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/routes/crud" "crypto/md5" "encoding/hex" "github.com/dgrijalva/jwt-go" @@ -41,7 +42,7 @@ func Login(c echo.Context) error { // Check user user, err := models.CheckUserCredentials(&u) if err != nil { - return c.JSON(http.StatusUnauthorized, models.Message{"Wrong username or password."}) + return crud.HandleHTTPError(err) } // Create token