Various user fixes (#38)

This commit is contained in:
konrad 2018-12-19 21:05:25 +00:00 committed by Gitea
parent 3e4f7fb2f4
commit cbc5995ad3
16 changed files with 55 additions and 51 deletions

View File

@ -237,9 +237,9 @@ Teams sind global, d.h. Ein Team kann mehrere Namespaces verwalten.
### Bugfixes ### Bugfixes
* [ ] Panic wenn mailer nicht erreichbar -> Als workaround mailer deaktivierbar machen, bzw keine mails verschicken * [x] Panic wenn mailer nicht erreichbar -> Als workaround mailer deaktivierbar machen, bzw keine mails verschicken
* [x] "unexpected EOF" * [x] "unexpected EOF"
* [ ] Beim Login & Password reset gibt die API zurück dass der Nutzer nicht existiert * [x] Beim Login & Password reset gibt die API zurück dass der Nutzer nicht existiert
### Docs ### Docs

View File

@ -27,7 +27,7 @@ Content-Type: application/json
Accept: application/json Accept: application/json
{ {
"user_name": "user" "email": "k@knt.li"
} }
### Request a token to reset a password ### Request a token to reset a password

View File

@ -54,6 +54,8 @@ redis:
db: 0 db: 0
mailer: mailer:
# Whether to enable the mailer or not. If it is disabled, all users are enabled right away and password reset is not possible.
enabled: false
# SMTP Host # SMTP Host
host: "" host: ""
# SMTP Host port # SMTP Host port

View File

@ -78,6 +78,8 @@ redis:
db: 0 db: 0
mailer: mailer:
# Whether to enable the mailer or not. If it is disabled, all users are enabled right away and password reset is not possible.
enabled: false
# SMTP Host # SMTP Host
host: "" host: ""
# SMTP Host port # SMTP Host port

View File

@ -63,6 +63,7 @@ func init() {
viper.SetDefault("cache.type", "memory") viper.SetDefault("cache.type", "memory")
viper.SetDefault("cache.maxelementsize", 1000) viper.SetDefault("cache.maxelementsize", 1000)
// Mailer // Mailer
viper.SetDefault("mailer.enabled", false)
viper.SetDefault("mailer.host", "") viper.SetDefault("mailer.host", "")
viper.SetDefault("mailer.port", "587") viper.SetDefault("mailer.port", "587")
viper.SetDefault("mailer.user", "user") viper.SetDefault("mailer.user", "user")

View File

@ -31,6 +31,10 @@ var Queue chan *gomail.Message
func StartMailDaemon() { func StartMailDaemon() {
Queue = make(chan *gomail.Message, viper.GetInt("mailer.queuelength")) Queue = make(chan *gomail.Message, viper.GetInt("mailer.queuelength"))
if !viper.GetBool("mailer.enabled") {
return
}
if viper.GetString("mailer.host") == "" { if viper.GetString("mailer.host") == "" {
log.Log.Warning("Mailer seems to be not configured! Please see the config docs for more details.") log.Log.Warning("Mailer seems to be not configured! Please see the config docs for more details.")
return return

View File

@ -2,20 +2,27 @@
id: 1 id: 1
username: 'user1' username: 'user1'
password: '1234' password: '1234'
email: 'johndoe@example.com' email: 'user1@example.com'
- -
id: 2 id: 2
username: 'user2' username: 'user2'
password: '1234' password: '1234'
email: 'johndoe@example.com' email: 'user2@example.com'
- -
id: 3 id: 3
username: 'user3' username: 'user3'
password: '1234' password: '1234'
email: 'johndoe@example.com' email: 'user3@example.com'
- -
id: 4 id: 4
username: 'user4' username: 'user4'
password: '1234' password: '1234'
email: 'johndoe@example.com' email: 'user4@example.com'
email_confirm_token: tiepiQueed8ahc7zeeFe1eveiy4Ein8osooxegiephauph2Ael email_confirm_token: tiepiQueed8ahc7zeeFe1eveiy4Ein8osooxegiephauph2Ael
-
id: 5
username: 'user5'
password: '1234'
email: 'user4@example.com'
email_confirm_token: tiepiQueed8ahc7zeeFe1eveiy4Ein8osooxegiephauph2Ael
is_active: false

View File

@ -160,7 +160,7 @@ func TestListUser_ReadAll(t *testing.T) {
ID: 1, ID: 1,
Username: "user1", Username: "user1",
Password: "1234", Password: "1234",
Email: "johndoe@example.com", Email: "user1@example.com",
}, },
Right: UserRightRead, Right: UserRightRead,
}, },
@ -169,7 +169,7 @@ func TestListUser_ReadAll(t *testing.T) {
ID: 2, ID: 2,
Username: "user2", Username: "user2",
Password: "1234", Password: "1234",
Email: "johndoe@example.com", Email: "user2@example.com",
}, },
Right: UserRightRead, Right: UserRightRead,
}, },

View File

@ -161,7 +161,7 @@ func TestNamespaceUser_ReadAll(t *testing.T) {
ID: 1, ID: 1,
Username: "user1", Username: "user1",
Password: "1234", Password: "1234",
Email: "johndoe@example.com", Email: "user1@example.com",
}, },
Right: UserRightRead, Right: UserRightRead,
}, },
@ -170,7 +170,7 @@ func TestNamespaceUser_ReadAll(t *testing.T) {
ID: 2, ID: 2,
Username: "user2", Username: "user2",
Password: "1234", Password: "1234",
Email: "johndoe@example.com", Email: "user2@example.com",
}, },
Right: UserRightRead, Right: UserRightRead,
}, },

View File

@ -30,10 +30,6 @@ import (
"testing" "testing"
) )
// IsTesting is set to true when we're running tests.
// We don't have a good solution to test email sending yet, so we disable email sending when testing
var IsTesting bool
// MainTest creates the test engine // MainTest creates the test engine
func MainTest(m *testing.M, pathToRoot string) { func MainTest(m *testing.M, pathToRoot string) {
var err error var err error
@ -42,8 +38,6 @@ func MainTest(m *testing.M, pathToRoot string) {
log.Log.Fatalf("Error creating test engine: %v\n", err) log.Log.Fatalf("Error creating test engine: %v\n", err)
} }
IsTesting = true
// Start the pseudo mail queue // Start the pseudo mail queue
mail.StartMailDaemon() mail.StartMailDaemon()

View File

@ -78,9 +78,9 @@ func getUserWithError(a web.Auth) (*User, error) {
// APIUserPassword represents a user object without timestamps and a json password field. // APIUserPassword represents a user object without timestamps and a json password field.
type APIUserPassword struct { type APIUserPassword struct {
ID int64 `json:"id"` ID int64 `json:"id"`
Username string `json:"username"` Username string `json:"username" valid:"length(3|250)"`
Password string `json:"password"` Password string `json:"password" valid:"length(8|250)"`
Email string `json:"email"` Email string `json:"email" valid:"email,length(0|250)"`
} }
// APIFormat formats an API User into a normal user struct // APIFormat formats an API User into a normal user struct
@ -125,7 +125,9 @@ 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 // hashing the password takes a long time, so we hash something to not make it clear if the username was wrong
bcrypt.GenerateFromPassword([]byte(u.Username), 14)
return User{}, ErrWrongUsernameOrPassword{}
} }
// User is invalid if it needs to verify its email address // User is invalid if it needs to verify its email address

View File

@ -20,6 +20,7 @@ import (
"code.vikunja.io/api/pkg/mail" "code.vikunja.io/api/pkg/mail"
"code.vikunja.io/api/pkg/metrics" "code.vikunja.io/api/pkg/metrics"
"code.vikunja.io/api/pkg/utils" "code.vikunja.io/api/pkg/utils"
"github.com/spf13/viper"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
@ -67,11 +68,13 @@ func CreateUser(user User) (newUser User, err error) {
return User{}, err return User{}, err
} }
// Generate a confirm token newUser.IsActive = true
newUser.EmailConfirmToken = utils.MakeRandomString(400) if viper.GetBool("mailer.enabled") {
// The new user should not be activated until it confirms his mail address
// The new user should not be activated until it confirms his mail address newUser.IsActive = false
newUser.IsActive = false // Generate a confirm token
newUser.EmailConfirmToken = utils.MakeRandomString(400)
}
// Insert it // Insert it
_, err = x.Insert(newUser) _, err = x.Insert(newUser)
@ -96,7 +99,7 @@ func CreateUser(user User) (newUser User, err error) {
} }
// Dont send a mail if we're testing // Dont send a mail if we're testing
if IsTesting { if !viper.GetBool("mailer.enabled") {
return newUserOut, err return newUserOut, err
} }

View File

@ -19,6 +19,7 @@ package models
import ( import (
"code.vikunja.io/api/pkg/mail" "code.vikunja.io/api/pkg/mail"
"code.vikunja.io/api/pkg/utils" "code.vikunja.io/api/pkg/utils"
"github.com/spf13/viper"
) )
// PasswordReset holds the data to reset a password // PasswordReset holds the data to reset a password
@ -59,7 +60,7 @@ func UserPasswordReset(reset *PasswordReset) (err error) {
} }
// Dont send a mail if we're testing // Dont send a mail if we're testing
if IsTesting { if !viper.GetBool("mailer.enabled") {
return return
} }
@ -75,13 +76,13 @@ func UserPasswordReset(reset *PasswordReset) (err error) {
// PasswordTokenRequest defines the request format for password reset resqest // PasswordTokenRequest defines the request format for password reset resqest
type PasswordTokenRequest struct { type PasswordTokenRequest struct {
Username string `json:"user_name"` Email string `json:"email" valid:"email,length(0|250)"`
} }
// RequestUserPasswordResetToken inserts a random token to reset a users password into the databsse // RequestUserPasswordResetToken inserts a random token to reset a users password into the databsse
func RequestUserPasswordResetToken(tr *PasswordTokenRequest) (err error) { func RequestUserPasswordResetToken(tr *PasswordTokenRequest) (err error) {
// Check if the user exists // Check if the user exists
user, err := GetUser(User{Username: tr.Username}) user, err := GetUser(User{Email: tr.Email})
if err != nil { if err != nil {
return return
} }
@ -96,7 +97,7 @@ func RequestUserPasswordResetToken(tr *PasswordTokenRequest) (err error) {
} }
// Dont send a mail if we're testing // Dont send a mail if we're testing
if IsTesting { if !viper.GetBool("mailer.enabled") {
return return
} }

View File

@ -78,7 +78,7 @@ func TestCreateUser(t *testing.T) {
assert.True(t, IsErrUserDoesNotExist(err)) assert.True(t, IsErrUserDoesNotExist(err))
// Check the user credentials with an unverified email // Check the user credentials with an unverified email
user, err := CheckUserCredentials(&UserLogin{"testuu", "1234"}) user, err := CheckUserCredentials(&UserLogin{"user5", "1234"})
assert.Error(t, err) assert.Error(t, err)
assert.True(t, IsErrEmailNotConfirmed(err)) assert.True(t, IsErrEmailNotConfirmed(err))
@ -97,7 +97,7 @@ func TestCreateUser(t *testing.T) {
// 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"})
assert.Error(t, err) assert.Error(t, err)
assert.True(t, IsErrUserDoesNotExist(err)) assert.True(t, IsErrWrongUsernameOrPassword(err))
// Update the user // Update the user
uuser, err := UpdateUser(User{ID: theuser.ID, Password: "444444"}) uuser, err := UpdateUser(User{ID: theuser.ID, Password: "444444"})
@ -146,7 +146,7 @@ func TestCreateUser(t *testing.T) {
func TestUserPasswordReset(t *testing.T) { func TestUserPasswordReset(t *testing.T) {
// Request a new token // Request a new token
tr := &PasswordTokenRequest{ tr := &PasswordTokenRequest{
Username: "user1", Email: "user1@example.com",
} }
err := RequestUserPasswordResetToken(tr) err := RequestUserPasswordResetToken(tr)
assert.NoError(t, err) assert.NoError(t, err)

View File

@ -67,6 +67,10 @@ func UserRequestResetPasswordToken(c echo.Context) error {
return echo.NewHTTPError(http.StatusBadRequest, "No username provided.") return echo.NewHTTPError(http.StatusBadRequest, "No username provided.")
} }
if err := c.Validate(pwTokenReset); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err)
}
err := models.RequestUserPasswordResetToken(&pwTokenReset) err := models.RequestUserPasswordResetToken(&pwTokenReset)
if err != nil { if err != nil {
return handler.HandleHTTPError(err, c) return handler.HandleHTTPError(err, c)

View File

@ -14,22 +14,6 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
// Vikunja is a todo-list application to facilitate your life.
// Copyright 2018 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// @title Vikunja API // @title Vikunja API
// @license.name GPLv3 // @license.name GPLv3
// @BasePath /api/v1 // @BasePath /api/v1