--- 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