fix(api): in-game identity resolution and link takeover ignore dead accounts
UserByMCUUID now resolves only live accounts: claim, menu, wake authorization, op-login vouch and the QR link-status poll treat a disabled or soft-deleted link holder exactly like an unlinked UUID instead of a retired identity. VerifyLinkCode lets a soft-deleted link be taken over by a fresh in-game code (the deleted account is gone, e.g. a migrated source), while a disabled holder still 409s so the lockout is not bypassable; failed attempts still do not consume the code. Fake repo and pgint coverage pin both branches.
This commit is contained in:
5 files changed
+182
-7
No files matched your search
@@ -287,7 +287,13 @@ func (f *fakeRepo) VerifyLinkCode(_ context.Context, userID, code string, now ti
|
||||
return "", "", ErrLinkCodeInvalid
|
||||
}
|
||||
if existing, ok := f.links[rec.mcUUID]; ok && existing != userID {
|
||||
return "", "", ErrConflict // do not consume another user's pending code
|
||||
// A soft-deleted link's identity is unclaimed: the fresh in-game code lets a
|
||||
// live caller take it over (mirrors PGRepo). Disabled-but-not-deleted stays a
|
||||
// conflict — takeover there would bypass the lockout. Neither arm consumes
|
||||
// the code.
|
||||
if !f.seededDeleted(existing) {
|
||||
return "", "", ErrConflict
|
||||
}
|
||||
}
|
||||
f.links[rec.mcUUID] = userID
|
||||
f.linkAuthSource[rec.mcUUID] = rec.authSource // copy/refresh, mirrors DO UPDATE
|
||||
@@ -626,7 +632,7 @@ func (f *fakeRepo) UUIDInAllowlist(_ context.Context, n, uuid string) (bool, err
|
||||
return f.allowUUID[n][uuid], nil
|
||||
}
|
||||
func (f *fakeRepo) UserByMCUUID(_ context.Context, uuid string) (string, error) {
|
||||
if u, ok := f.links[uuid]; ok {
|
||||
if u, ok := f.links[uuid]; ok && !f.seededDead(u) {
|
||||
return u, nil
|
||||
}
|
||||
return "", ErrNotFound
|
||||
@@ -1177,6 +1183,20 @@ func (f *fakeRepo) seededDead(id string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// seededDeleted is the narrower liveness query: soft-deleted only (a disabled
|
||||
// account still holds its identity, mirroring VerifyLinkCode's takeover rule).
|
||||
func (f *fakeRepo) seededDeleted(id string) bool {
|
||||
if f.deletedIDs[id] {
|
||||
return true
|
||||
}
|
||||
for _, su := range f.seededUsers {
|
||||
if su.view.ID == id {
|
||||
return su.detail.DeletedAt != nil
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (f *fakeRepo) GetQuotas(_ context.Context, userID string) (*QuotaView, error) {
|
||||
if !f.liveUserExists(userID) {
|
||||
return nil, ErrNotFound
|
||||
|
||||
Reference in new issue
Block a user