Prevent login from inactive (aka non-verified) users (#8)
This commit is contained in:
parent
301a4eedda
commit
4713023a97
6 changed files with 68 additions and 5 deletions
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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. |
|
||||||
|
|
|
@ -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
|
||||||
// ===================
|
// ===================
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"})
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue