Prevent login from inactive (aka non-verified) users (#8)

This commit is contained in:
konrad 2018-11-01 22:47:41 +00:00 committed by Gitea
parent 301a4eedda
commit 4713023a97
6 changed files with 68 additions and 5 deletions

View file

@ -3,7 +3,7 @@ POST http://localhost:8080/api/v1/login
Content-Type: application/json Content-Type: application/json
{ {
"user": "user", "username": "user",
"password": "1234" "password": "1234"
} }

View file

@ -11,6 +11,9 @@ This document describes the different errors Vikunja can return.
| 1006 | 400 | Could not get the user id. | | 1006 | 400 | Could not get the user id. |
| 1008 | 412 | No password reset token provided. | | 1008 | 412 | No password reset token provided. |
| 1009 | 412 | Invalid password reset token. | | 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. | | 2001 | 400 | ID cannot be empty or 0. |
| 3001 | 404 | The list does not exist. | | 3001 | 404 | The list does not exist. |
| 3004 | 403 | The user needs to have read permissions on that list to perform that action. | | 3004 | 403 | The user needs to have read permissions on that list to perform that action. |

View file

@ -197,6 +197,51 @@ func IsErrInvalidEmailConfirmToken(err error) bool {
return ok 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 // Empty things errors
// =================== // ===================

View file

@ -74,17 +74,23 @@ func GetUser(user User) (userOut User, err error) {
// CheckUserCredentials checks user credentials // CheckUserCredentials checks user credentials
func CheckUserCredentials(u *UserLogin) (User, error) { func CheckUserCredentials(u *UserLogin) (User, error) {
// Check if the user exists // Check if the user exists
user, err := GetUser(User{Username: u.Username}) user, err := GetUser(User{Username: u.Username})
if err != nil { if err != nil {
return User{}, err 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 // Check the users password
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(u.Password)) err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(u.Password))
if err != nil { if err != nil {
if err == bcrypt.ErrMismatchedHashAndPassword {
return User{}, ErrWrongUsernameOrPassword{}
}
return User{}, err return User{}, err
} }

View file

@ -61,14 +61,22 @@ func TestCreateUser(t *testing.T) {
assert.Error(t, err) assert.Error(t, err)
assert.True(t, IsErrUserDoesNotExist(err)) assert.True(t, IsErrUserDoesNotExist(err))
// Check the user credentials // Check the user credentials with an unverified email
user, err := CheckUserCredentials(&UserLogin{"testuu", "1234"}) 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.NoError(t, err)
assert.Equal(t, "testuu", user.Username) assert.Equal(t, "testuu", user.Username)
// Check wrong password (should also fail) // Check wrong password (should also fail)
_, err = CheckUserCredentials(&UserLogin{"testuu", "12345"}) _, err = CheckUserCredentials(&UserLogin{"testuu", "12345"})
assert.Error(t, err) assert.Error(t, err)
assert.True(t, IsErrWrongUsernameOrPassword(err))
// Check usercredentials for a nonexistent user (should fail) // Check usercredentials for a nonexistent user (should fail)
_, err = CheckUserCredentials(&UserLogin{"dfstestuu", "1234"}) _, err = CheckUserCredentials(&UserLogin{"dfstestuu", "1234"})

View file

@ -2,6 +2,7 @@ package v1
import ( import (
"code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/routes/crud"
"crypto/md5" "crypto/md5"
"encoding/hex" "encoding/hex"
"github.com/dgrijalva/jwt-go" "github.com/dgrijalva/jwt-go"
@ -41,7 +42,7 @@ func Login(c echo.Context) error {
// Check user // Check user
user, err := models.CheckUserCredentials(&u) user, err := models.CheckUserCredentials(&u)
if err != nil { if err != nil {
return c.JSON(http.StatusUnauthorized, models.Message{"Wrong username or password."}) return crud.HandleHTTPError(err)
} }
// Create token // Create token