fix: add postgres triggers to remove deleted users from user_links (#12117)

* chore: add database test fixture to insert non-unique linked_ids
* chore: create unit test to exercise failed email change bug
* fix: add postgres triggers to keep user_links clear of deleted users
* Add migrations to prevent deleted users with links
* Force soft delete of users, do not allow un-delete
This commit is contained in:
Steven Masley
2024-02-20 13:19:38 -06:00
committed by GitHub
parent b342bd7869
commit 2dac34276a
16 changed files with 200 additions and 89 deletions

View File

@ -733,6 +733,18 @@ func isNotNull(v interface{}) bool {
return reflect.ValueOf(v).FieldByName("Valid").Bool()
}
// Took the error from the real database.
var deletedUserLinkError = &pq.Error{
Severity: "ERROR",
// "raise_exception" error
Code: "P0001",
Message: "Cannot create user_link for deleted user",
Where: "PL/pgSQL function insert_user_links_fail_if_user_deleted() line 7 at RAISE",
File: "pl_exec.c",
Line: "3864",
Routine: "exec_stmt_raise",
}
func (*FakeQuerier) AcquireLock(_ context.Context, _ int64) error {
return xerrors.New("AcquireLock must only be called within a transaction")
}
@ -5560,6 +5572,10 @@ func (q *FakeQuerier) InsertUserLink(_ context.Context, args database.InsertUser
q.mutex.Lock()
defer q.mutex.Unlock()
if u, err := q.getUserByIDNoLock(args.UserID); err == nil && u.Deleted {
return database.UserLink{}, deletedUserLinkError
}
//nolint:gosimple
link := database.UserLink{
UserID: args.UserID,
@ -6724,33 +6740,22 @@ func (q *FakeQuerier) UpdateUserAppearanceSettings(_ context.Context, arg databa
return database.User{}, sql.ErrNoRows
}
func (q *FakeQuerier) UpdateUserDeletedByID(_ context.Context, params database.UpdateUserDeletedByIDParams) error {
if err := validateDatabaseType(params); err != nil {
return err
}
func (q *FakeQuerier) UpdateUserDeletedByID(_ context.Context, id uuid.UUID) error {
q.mutex.Lock()
defer q.mutex.Unlock()
for i, u := range q.users {
if u.ID == params.ID {
u.Deleted = params.Deleted
if u.ID == id {
u.Deleted = true
q.users[i] = u
// NOTE: In the real world, this is done by a trigger.
i := 0
for {
if i >= len(q.apiKeys) {
break
}
k := q.apiKeys[i]
if k.UserID == u.ID {
q.apiKeys[i] = q.apiKeys[len(q.apiKeys)-1]
q.apiKeys = q.apiKeys[:len(q.apiKeys)-1]
// We removed an element, so decrement
i--
}
i++
}
q.apiKeys = slices.DeleteFunc(q.apiKeys, func(u database.APIKey) bool {
return id == u.UserID
})
q.userLinks = slices.DeleteFunc(q.userLinks, func(u database.UserLink) bool {
return id == u.UserID
})
return nil
}
}
@ -6804,6 +6809,10 @@ func (q *FakeQuerier) UpdateUserLink(_ context.Context, params database.UpdateUs
q.mutex.Lock()
defer q.mutex.Unlock()
if u, err := q.getUserByIDNoLock(params.UserID); err == nil && u.Deleted {
return database.UserLink{}, deletedUserLinkError
}
for i, link := range q.userLinks {
if link.UserID == params.UserID && link.LoginType == params.LoginType {
link.OAuthAccessToken = params.OAuthAccessToken