Various user fixes (#38)
This commit is contained in:
parent
3e4f7fb2f4
commit
cbc5995ad3
16 changed files with 55 additions and 51 deletions
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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,
|
||||||
},
|
},
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue