mirror of
https://github.com/go-vikunja/vikunja.git
synced 2026-04-28 18:38:24 -05:00
fix(user): ensure deletion tokens can only be used by the user who created them
This commit is contained in:
@@ -16,3 +16,9 @@
|
||||
token: 'tiepiQueed8ahc7zeeFe1eveiy4Ein8osooxegiephauph2Aei'
|
||||
kind: 2
|
||||
created: 2021-07-12 00:00:13
|
||||
-
|
||||
id: 4
|
||||
user_id: 1
|
||||
token: 'deletiontesttoken'
|
||||
kind: 3
|
||||
created: 2021-07-12 00:00:14
|
||||
|
||||
@@ -106,8 +106,16 @@ func ConfirmDeletion(s *xorm.Session, user *User, token string) (err error) {
|
||||
}
|
||||
|
||||
if tk == nil {
|
||||
// TODO: return invalid token error
|
||||
return
|
||||
return ErrInvalidDeletionToken{
|
||||
Token: token,
|
||||
}
|
||||
}
|
||||
|
||||
if tk.UserID != user.ID {
|
||||
return ErrTokenUserMismatch{
|
||||
TokenUserID: tk.UserID,
|
||||
UserID: user.ID,
|
||||
}
|
||||
}
|
||||
|
||||
err = removeTokens(s, user, TokenAccountDeletion)
|
||||
|
||||
@@ -659,3 +659,58 @@ func (err ErrInvalidUserContext) HTTPError() web.HTTPError {
|
||||
Message: "Invalid user context. Please make sure the passed token is valid and try again.",
|
||||
}
|
||||
}
|
||||
|
||||
// ErrInvalidDeletionToken represents an error where the deletion token is invalid
|
||||
type ErrInvalidDeletionToken struct {
|
||||
Token string
|
||||
}
|
||||
|
||||
// IsErrInvalidDeletionToken checks if an error is a ErrInvalidDeletionToken.
|
||||
func IsErrInvalidDeletionToken(err error) bool {
|
||||
_, ok := err.(ErrInvalidDeletionToken)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrInvalidDeletionToken) Error() string {
|
||||
return fmt.Sprintf("Invalid deletion token [Token: %s]", err.Token)
|
||||
}
|
||||
|
||||
// ErrorCodeInvalidDeletionToken holds the unique world-error code of this error
|
||||
const ErrorCodeInvalidDeletionToken = 1028
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrInvalidDeletionToken) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{
|
||||
HTTPCode: http.StatusBadRequest,
|
||||
Code: ErrorCodeInvalidDeletionToken,
|
||||
Message: "Invalid deletion token provided.",
|
||||
}
|
||||
}
|
||||
|
||||
// ErrTokenUserMismatch represents an error where the token doesn't belong to the user
|
||||
type ErrTokenUserMismatch struct {
|
||||
TokenUserID int64
|
||||
UserID int64
|
||||
}
|
||||
|
||||
// IsErrTokenUserMismatch checks if an error is a ErrTokenUserMismatch.
|
||||
func IsErrTokenUserMismatch(err error) bool {
|
||||
_, ok := err.(ErrTokenUserMismatch)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrTokenUserMismatch) Error() string {
|
||||
return fmt.Sprintf("Token user mismatch [Token User ID: %d, User ID: %d]", err.TokenUserID, err.UserID)
|
||||
}
|
||||
|
||||
// ErrorCodeTokenUserMismatch holds the unique world-error code of this error
|
||||
const ErrorCodeTokenUserMismatch = 1029
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrTokenUserMismatch) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{
|
||||
HTTPCode: http.StatusForbidden,
|
||||
Code: ErrorCodeTokenUserMismatch,
|
||||
Message: "This deletion token does not belong to your account.",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -597,3 +597,62 @@ func TestUserPasswordReset(t *testing.T) {
|
||||
assert.True(t, IsErrInvalidPasswordResetToken(err))
|
||||
})
|
||||
}
|
||||
|
||||
func TestConfirmDeletion(t *testing.T) {
|
||||
t.Run("normal", func(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
user := &User{ID: 1}
|
||||
err := ConfirmDeletion(s, user, "deletiontesttoken")
|
||||
require.NoError(t, err)
|
||||
|
||||
updatedUser, err := GetUserByID(s, 1)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, updatedUser.DeletionScheduledAt.IsZero())
|
||||
})
|
||||
t.Run("invalid token", func(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
user := &User{ID: 1}
|
||||
err := ConfirmDeletion(s, user, "invalidtoken")
|
||||
require.Error(t, err)
|
||||
assert.True(t, IsErrInvalidDeletionToken(err))
|
||||
|
||||
invalidErr := err.(ErrInvalidDeletionToken)
|
||||
assert.Equal(t, "invalidtoken", invalidErr.Token)
|
||||
})
|
||||
t.Run("token user mismatch", func(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
user := &User{ID: 3}
|
||||
err := ConfirmDeletion(s, user, "deletiontesttoken")
|
||||
require.Error(t, err)
|
||||
assert.True(t, IsErrTokenUserMismatch(err))
|
||||
|
||||
mismatchErr := err.(ErrTokenUserMismatch)
|
||||
assert.Equal(t, int64(1), mismatchErr.TokenUserID)
|
||||
assert.Equal(t, int64(3), mismatchErr.UserID)
|
||||
})
|
||||
t.Run("removes token after successful confirmation", func(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
token := "deletiontesttoken"
|
||||
|
||||
user := &User{ID: 1}
|
||||
err := ConfirmDeletion(s, user, token)
|
||||
require.NoError(t, err)
|
||||
|
||||
db.AssertMissing(t, "user_tokens", map[string]interface{}{
|
||||
"token": token,
|
||||
"kind": TokenAccountDeletion,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user