diff --git a/pkg/db/fixtures/user_tokens.yml b/pkg/db/fixtures/user_tokens.yml index 95080ba3f..f676297bd 100644 --- a/pkg/db/fixtures/user_tokens.yml +++ b/pkg/db/fixtures/user_tokens.yml @@ -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 diff --git a/pkg/user/delete.go b/pkg/user/delete.go index 8fd55e2a5..13f61f8d2 100644 --- a/pkg/user/delete.go +++ b/pkg/user/delete.go @@ -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) diff --git a/pkg/user/error.go b/pkg/user/error.go index 398802c61..9e0517206 100644 --- a/pkg/user/error.go +++ b/pkg/user/error.go @@ -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.", + } +} diff --git a/pkg/user/user_test.go b/pkg/user/user_test.go index e4661a28d..ce106c543 100644 --- a/pkg/user/user_test.go +++ b/pkg/user/user_test.go @@ -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, + }) + }) +}