修复全量审查问题;设置接口PATCH化;回传指纹加固
CI / test (push) Successful in 32s

This commit is contained in:
2026-07-22 16:51:23 +08:00
parent 0614ef22af
commit f51fb6c722
66 changed files with 3997 additions and 687 deletions
+39 -18
View File
@@ -329,27 +329,48 @@ func (o *OAuthService) Identities(ctx context.Context, username string) ([]model
}
// Unbind 解绑外部身份(校验归属);密码登录被禁用时不允许解绑最后一个身份,防自锁。
// 检查与删除在同一事务内并锁定用户行,防与禁用密码登录并发绕过「至少一种登录方式」。
func (o *OAuthService) Unbind(ctx context.Context, username string, id uint) error {
user, err := o.auth.findUser(ctx, username)
if err != nil {
return err
}
n, err := o.auth.identityCount(ctx, user.ID)
if err != nil {
return err
}
if n <= 1 {
if off, err := o.auth.PasswordLoginDisabled(ctx); err == nil && off {
return ErrLastIdentity
err := o.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
user, err := lockUserForAuthChange(tx, username)
if err != nil {
return err
}
}
res := o.db.WithContext(ctx).Where("id = ? AND user_id = ?", id, user.ID).Delete(&model.UserIdentity{})
if res.Error != nil {
return fmt.Errorf("unbind identity: %w", res.Error)
}
if res.RowsAffected == 0 {
return gorm.ErrRecordNotFound
if err := ensureNotLastLogin(tx, user.ID); err != nil {
return err
}
res := tx.Where("id = ? AND user_id = ?", id, user.ID).Delete(&model.UserIdentity{})
if res.Error != nil {
return fmt.Errorf("unbind identity: %w", res.Error)
}
if res.RowsAffected == 0 {
return gorm.ErrRecordNotFound
}
return nil
})
if err != nil {
return err
}
// 解绑属敏感变更:递增令牌版本,已签发会话全部失效
return o.auth.bumpTokenVersion(ctx, username)
}
// ensureNotLastLogin 事务内校验不变量:仅剩一个身份且密码登录已禁用时拒绝解绑;
// 开关读取失败按失败关闭处理(返回错误),不允许失败放行造成自锁。
func ensureNotLastLogin(tx *gorm.DB, userID uint) error {
n, err := identityCountTx(tx, userID)
if err != nil {
return err
}
if n > 1 {
return nil
}
off, err := settingValueTx(tx, settingSecPasswordLoginOff)
if err != nil {
return err
}
if off == "1" {
return ErrLastIdentity
}
return nil
}