All checks were successful
Build docker and publish / build (20.15.1) (push) Successful in 7m36s
145 lines
5.7 KiB
Markdown
145 lines
5.7 KiB
Markdown
---
|
|
name: soft-delete-relogin-consistency
|
|
description: |
|
|
Fix for missing auth/identity records after account deletion + device re-login.
|
|
Use when: (1) User deletes account but device records are intentionally kept
|
|
(e.g., to prevent trial abuse), (2) Re-login via device succeeds but user
|
|
appears to have wrong identity type, (3) Frontend shows incorrect UI because
|
|
auth_methods or similar identity records are empty/wrong after re-login,
|
|
(4) Soft-deleted records cause stale cache entries that misrepresent user state.
|
|
Covers GORM soft-delete, device-based auth, cache invalidation after re-creation.
|
|
author: Codex
|
|
version: 1.0.0
|
|
date: 2026-03-11
|
|
---
|
|
|
|
# Soft-Delete + Re-Login Auth Consistency
|
|
|
|
## Problem
|
|
|
|
When a system uses soft-delete for auth/identity records during account deletion but
|
|
intentionally keeps primary records (like device records) for abuse prevention, re-login
|
|
flows may succeed at the "find existing record" step but fail to re-create the
|
|
soft-deleted identity records. This causes the user to exist in an inconsistent state
|
|
where they're authenticated but missing critical identity metadata.
|
|
|
|
## Context / Trigger Conditions
|
|
|
|
- Account deletion (注销) soft-deletes `auth_methods` (or equivalent identity records)
|
|
- Device/hardware records are intentionally kept to prevent trial reward abuse
|
|
- Device-based re-login finds existing device record -> reuses old user_id
|
|
- But the "device found" code path skips identity record creation (only the
|
|
"device not found" registration path creates them)
|
|
- Result: User is logged in but `auth_methods` is empty or missing the expected type
|
|
- Frontend UI breaks because it relies on `auth_methods[0].auth_type` to determine
|
|
login mode and show/hide UI elements
|
|
|
|
### Symptoms
|
|
|
|
- Buttons or UI elements that should be hidden for device-only users appear after
|
|
account deletion + re-login
|
|
- API returns user info with empty or unexpected `auth_methods` array
|
|
- `isDeviceLogin()` or similar identity checks return wrong results
|
|
- Cache returns stale user data even after re-login
|
|
|
|
## Solution
|
|
|
|
### Step 1: Identify the re-login code path
|
|
|
|
Find the "device found" branch in the login logic. This is the code path that runs
|
|
when a device record already exists (as opposed to the registration path).
|
|
|
|
### Step 2: Add identity record existence check
|
|
|
|
After finding the user via device record, check if the expected identity record exists:
|
|
|
|
```go
|
|
// After finding user via existing device record
|
|
hasDeviceAuth := false
|
|
for _, am := range userInfo.AuthMethods {
|
|
if am.AuthType == "device" && am.AuthIdentifier == req.Identifier {
|
|
hasDeviceAuth = true
|
|
break
|
|
}
|
|
}
|
|
if !hasDeviceAuth {
|
|
// Re-create the soft-deleted auth record
|
|
authMethod := &user.AuthMethods{
|
|
UserId: userInfo.Id,
|
|
AuthType: "device",
|
|
AuthIdentifier: req.Identifier,
|
|
Verified: true,
|
|
}
|
|
if createErr := db.Create(authMethod).Error; createErr != nil {
|
|
log.Error("re-create auth method failed", err)
|
|
} else {
|
|
// CRITICAL: Clear user cache so subsequent reads return updated data
|
|
_ = userModel.ClearUserCache(ctx, userInfo)
|
|
}
|
|
}
|
|
```
|
|
|
|
### Step 3: Ensure cache invalidation
|
|
|
|
After re-creating the identity record, clear the user cache. This is critical because
|
|
cached user data (with `Preload("AuthMethods")`) will still show the old empty state
|
|
until the cache is invalidated.
|
|
|
|
### Step 4: Verify GORM soft-delete behavior
|
|
|
|
GORM's soft-delete (`deleted_at IS NULL` filter) means:
|
|
- `Preload("AuthMethods")` will NOT return soft-deleted records
|
|
- `db.Create()` will create a NEW record (not undelete the old one)
|
|
- The old soft-deleted record remains in the database (harmless)
|
|
|
|
## Verification
|
|
|
|
1. Delete account (注销)
|
|
2. Re-login via device
|
|
3. Call user info API - verify `auth_methods` contains the device type
|
|
4. Check frontend UI - verify device-specific UI state is correct
|
|
|
|
## Example
|
|
|
|
**Before fix:**
|
|
```
|
|
1. User has auth_methods: [device_A, email_A]
|
|
2. User deletes account -> auth_methods all soft-deleted
|
|
3. Device record kept (abuse prevention)
|
|
4. User re-logins via same device
|
|
5. FindOneDeviceByIdentifier finds device -> reuses user_id
|
|
6. FindOne returns user with AuthMethods=[] (soft-deleted, filtered out)
|
|
7. Frontend: isDeviceLogin() = false (no auth_methods) -> shows wrong buttons
|
|
```
|
|
|
|
**After fix:**
|
|
```
|
|
1-4. Same as above
|
|
5. FindOneDeviceByIdentifier finds device -> reuses user_id
|
|
6. FindOne returns user with AuthMethods=[]
|
|
7. NEW: Detects missing device auth_method, re-creates it, clears cache
|
|
8. Frontend: isDeviceLogin() = true -> correct UI
|
|
```
|
|
|
|
## Notes
|
|
|
|
- This pattern applies broadly to any system where:
|
|
- Account deletion removes identity records but keeps usage records
|
|
- Re-login can succeed via the usage records
|
|
- UI/business logic depends on the identity records existing
|
|
- The "don't delete device records" design is intentional for preventing abuse
|
|
(e.g., users repeatedly deleting and re-creating accounts to get trial rewards)
|
|
- Cache invalidation is the most commonly missed step - without it, the fix appears
|
|
to not work because cached data is served until TTL expires
|
|
- Consider whether `Unscoped()` (GORM) should be used to also query soft-deleted
|
|
records, or whether re-creation is the better approach (usually re-creation is
|
|
cleaner as it creates a fresh record with correct timestamps)
|
|
|
|
## Related Patterns
|
|
|
|
- **Cache key dependency chains**: When `ClearUserCache` depends on `AuthMethods`
|
|
to generate email cache keys, capture auth_methods BEFORE deletion, then explicitly
|
|
clear derived cache keys after the transaction
|
|
- **Family ownership transfer**: When an owner exits a shared resource group, transfer
|
|
ownership to a remaining member instead of dissolving the group
|