fix: restore soft-deleted users on login
This commit is contained in:
@@ -88,6 +88,33 @@ func GetLogByTokenId(tokenId int) (logs []*Log, err error) {
|
||||
return logs, err
|
||||
}
|
||||
|
||||
// RecordUserRestoreLog writes an audit-log entry whenever a soft-deleted user
|
||||
// is automatically restored (e.g. by logging in again via password or OAuth).
|
||||
// `source` describes the trigger, e.g. "github", "linuxdo", "telegram", "password".
|
||||
// `callerIp` may be empty when the call originates from the model layer.
|
||||
func RecordUserRestoreLog(userId int, source string, callerIp string) {
|
||||
username, _ := GetUsernameById(userId, false)
|
||||
other := map[string]interface{}{}
|
||||
if source != "" {
|
||||
other["restore_source"] = source
|
||||
}
|
||||
if callerIp != "" {
|
||||
other["caller_ip"] = callerIp
|
||||
}
|
||||
log := &Log{
|
||||
UserId: userId,
|
||||
Username: username,
|
||||
CreatedAt: common.GetTimestamp(),
|
||||
Type: LogTypeSystem,
|
||||
Content: fmt.Sprintf("软删除用户被自动恢复,来源 %s", source),
|
||||
Ip: callerIp,
|
||||
Other: common.MapToJsonStr(other),
|
||||
}
|
||||
if err := LOG_DB.Create(log).Error; err != nil {
|
||||
common.SysLog("failed to record user restore log: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func RecordLog(userId int, logType int, content string) {
|
||||
if logType == LogTypeConsume && !common.LogConsumeEnabled {
|
||||
return
|
||||
|
||||
+38
-13
@@ -589,18 +589,40 @@ func (user *User) HardDelete() error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (user *User) Restore() error {
|
||||
if user.Id == 0 {
|
||||
return errors.New("id 为空!")
|
||||
}
|
||||
err := DB.Unscoped().Model(user).Update("deleted_at", nil).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := invalidateUserCache(user.Id); err != nil {
|
||||
return err
|
||||
}
|
||||
user.DeletedAt = gorm.DeletedAt{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (user *User) RestoreIfDeleted(source string, callerIp string) error {
|
||||
if !user.DeletedAt.Valid {
|
||||
return nil
|
||||
}
|
||||
if err := user.Restore(); err != nil {
|
||||
return err
|
||||
}
|
||||
RecordUserRestoreLog(user.Id, source, callerIp)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateAndFill check password & user status
|
||||
func (user *User) ValidateAndFill() (err error) {
|
||||
// When querying with struct, GORM will only query with non-zero fields,
|
||||
// that means if your field's value is 0, '', false or other zero values,
|
||||
// it won't be used to build query conditions
|
||||
func (user *User) ValidateAndFill(callerIp string) (err error) {
|
||||
password := user.Password
|
||||
username := strings.TrimSpace(user.Username)
|
||||
if username == "" || password == "" {
|
||||
return ErrUserEmptyCredentials
|
||||
}
|
||||
// find by username or email
|
||||
err = DB.Where("username = ? OR email = ?", username, username).First(user).Error
|
||||
err = DB.Unscoped().Where("username = ? OR email = ?", username, username).First(user).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return ErrInvalidCredentials
|
||||
@@ -611,6 +633,9 @@ func (user *User) ValidateAndFill() (err error) {
|
||||
if !okay || user.Status != common.UserStatusEnabled {
|
||||
return ErrInvalidCredentials
|
||||
}
|
||||
if err := user.RestoreIfDeleted("password", callerIp); err != nil {
|
||||
return fmt.Errorf("%w: %v", ErrDatabase, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -634,7 +659,7 @@ func (user *User) FillUserByGitHubId() error {
|
||||
if user.GitHubId == "" {
|
||||
return errors.New("GitHub id 为空!")
|
||||
}
|
||||
DB.Where(User{GitHubId: user.GitHubId}).First(user)
|
||||
DB.Unscoped().Where(User{GitHubId: user.GitHubId}).First(user)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -650,7 +675,7 @@ func (user *User) FillUserByDiscordId() error {
|
||||
if user.DiscordId == "" {
|
||||
return errors.New("discord id 为空!")
|
||||
}
|
||||
DB.Where(User{DiscordId: user.DiscordId}).First(user)
|
||||
DB.Unscoped().Where(User{DiscordId: user.DiscordId}).First(user)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -658,7 +683,7 @@ func (user *User) FillUserByOidcId() error {
|
||||
if user.OidcId == "" {
|
||||
return errors.New("oidc id 为空!")
|
||||
}
|
||||
DB.Where(User{OidcId: user.OidcId}).First(user)
|
||||
DB.Unscoped().Where(User{OidcId: user.OidcId}).First(user)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -666,7 +691,7 @@ func (user *User) FillUserByWeChatId() error {
|
||||
if user.WeChatId == "" {
|
||||
return errors.New("WeChat id 为空!")
|
||||
}
|
||||
DB.Where(User{WeChatId: user.WeChatId}).First(user)
|
||||
DB.Unscoped().Where(User{WeChatId: user.WeChatId}).First(user)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -674,7 +699,7 @@ func (user *User) FillUserByTelegramId() error {
|
||||
if user.TelegramId == "" {
|
||||
return errors.New("Telegram id 为空!")
|
||||
}
|
||||
err := DB.Where(User{TelegramId: user.TelegramId}).First(user).Error
|
||||
err := DB.Unscoped().Where(User{TelegramId: user.TelegramId}).First(user).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("该 Telegram 账户未绑定")
|
||||
}
|
||||
@@ -698,7 +723,7 @@ func IsDiscordIdAlreadyTaken(discordId string) bool {
|
||||
}
|
||||
|
||||
func IsOidcIdAlreadyTaken(oidcId string) bool {
|
||||
return DB.Where("oidc_id = ?", oidcId).Find(&User{}).RowsAffected == 1
|
||||
return DB.Unscoped().Where("oidc_id = ?", oidcId).Find(&User{}).RowsAffected == 1
|
||||
}
|
||||
|
||||
func IsTelegramIdAlreadyTaken(telegramId string) bool {
|
||||
@@ -1057,7 +1082,7 @@ func (user *User) FillUserByLinuxDOId() error {
|
||||
if user.LinuxDOId == "" {
|
||||
return errors.New("linux do id is empty")
|
||||
}
|
||||
err := DB.Where("linux_do_id = ?", user.LinuxDOId).First(user).Error
|
||||
err := DB.Unscoped().Where("linux_do_id = ?", user.LinuxDOId).First(user).Error
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@ import (
|
||||
// UserOAuthBinding stores the binding relationship between users and custom OAuth providers
|
||||
type UserOAuthBinding struct {
|
||||
Id int `json:"id" gorm:"primaryKey"`
|
||||
UserId int `json:"user_id" gorm:"not null;uniqueIndex:ux_user_provider"` // User ID - one binding per user per provider
|
||||
ProviderId int `json:"provider_id" gorm:"not null;uniqueIndex:ux_user_provider;uniqueIndex:ux_provider_userid"` // Custom OAuth provider ID
|
||||
ProviderUserId string `json:"provider_user_id" gorm:"type:varchar(256);not null;uniqueIndex:ux_provider_userid"` // User ID from OAuth provider - one OAuth account per provider
|
||||
UserId int `json:"user_id" gorm:"not null;uniqueIndex:ux_user_provider"` // User ID - one binding per user per provider
|
||||
ProviderId int `json:"provider_id" gorm:"not null;uniqueIndex:ux_user_provider;uniqueIndex:ux_provider_userid"` // Custom OAuth provider ID
|
||||
ProviderUserId string `json:"provider_user_id" gorm:"type:varchar(256);not null;uniqueIndex:ux_provider_userid"` // User ID from OAuth provider - one OAuth account per provider
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ func GetUserByOAuthBinding(providerId int, providerUserId string) (*User, error)
|
||||
}
|
||||
|
||||
var user User
|
||||
err = DB.First(&user, binding.UserId).Error
|
||||
err = DB.Unscoped().First(&user, binding.UserId).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user