feat: publish v1.0.0-rc.36-custom.1
Publish Docker image (Multi-arch) / Build & push (amd64) (push) Failing after 9m20s
Release (Linux, macOS, Windows) / Linux Release (push) Failing after 55m7s
Publish Docker image (Multi-arch) / Build & push (arm64) (push) Canceled after 0s
Release (Linux, macOS, Windows) / macOS Release (push) Canceled after 0s
Release (Linux, macOS, Windows) / Windows Release (push) Canceled after 0s
Publish Docker image (Multi-arch) / Create multi-arch manifests (push) Canceled after 0s
Publish Docker image (Multi-arch) / Build & push (amd64) (push) Failing after 9m20s
Release (Linux, macOS, Windows) / Linux Release (push) Failing after 55m7s
Publish Docker image (Multi-arch) / Build & push (arm64) (push) Canceled after 0s
Release (Linux, macOS, Windows) / macOS Release (push) Canceled after 0s
Release (Linux, macOS, Windows) / Windows Release (push) Canceled after 0s
Publish Docker image (Multi-arch) / Create multi-arch manifests (push) Canceled after 0s
This commit is contained in:
commit
bb7968b612
2381 files changed
+551494
No files matched your search
@@ -0,0 +1,151 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/QuantumNous/new-api/common"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrCurrentPasswordInvalid = errors.New("Current password is incorrect.")
|
||||
ErrAccountPasswordState = errors.New("Password settings have changed. Reload the page and try again.")
|
||||
ErrLastLoginMethod = errors.New("Add another login method before unlinking this account.")
|
||||
ErrAccountBindingChanged = errors.New("Account bindings have changed. Start this operation again.")
|
||||
ErrCannotDeleteRootUser = errors.New("The root account cannot be deleted.")
|
||||
)
|
||||
|
||||
// ChangeUserPassword rechecks the authorized session, password state and current
|
||||
// password under the same user lock as the credential update. Only self-profile
|
||||
// fields are accepted; a stale snapshot cannot restore roles or account status.
|
||||
func ChangeUserPassword(identity AuthSessionIdentity, update *User, firstPassword bool) error {
|
||||
hash, err := common.HashAccountPassword(update.Password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return DB.Transaction(func(tx *gorm.DB) error {
|
||||
if err := ValidateAuthSessionWithTx(tx, identity); err != nil {
|
||||
return err
|
||||
}
|
||||
var current User
|
||||
if err := tx.First(¤t, identity.UserID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if firstPassword != (current.Password == "") {
|
||||
return ErrAccountPasswordState
|
||||
}
|
||||
if !firstPassword && !common.ValidatePasswordAndHash(update.OriginalPassword, current.Password) {
|
||||
return ErrCurrentPasswordInvalid
|
||||
}
|
||||
if current.Password != "" && common.ValidatePasswordAndHash(update.Password, current.Password) {
|
||||
return common.ErrAccountPasswordSame
|
||||
}
|
||||
changes := map[string]any{"password": hash}
|
||||
if update.Username != "" {
|
||||
changes["username"] = update.Username
|
||||
}
|
||||
if update.DisplayName != "" {
|
||||
changes["display_name"] = update.DisplayName
|
||||
}
|
||||
if _, err := IncrementUserAuthVersionWithTx(tx, identity.UserID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Model(&User{}).Where("id = ?", identity.UserID).Updates(changes).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.First(update, identity.UserID).Error
|
||||
})
|
||||
}
|
||||
|
||||
// AccountLoginMethods is a snapshot of administrator-enabled login mechanisms.
|
||||
// Enrollment is checked inside the transaction, not trusted from this snapshot.
|
||||
type AccountLoginMethods struct {
|
||||
Password bool
|
||||
Passkey bool
|
||||
WeChat bool
|
||||
OAuthColumns []string
|
||||
CustomProviderIDs []int
|
||||
}
|
||||
|
||||
func UnbindUserOAuthForSession(identity AuthSessionIdentity, providerID int, enabled AccountLoginMethods) error {
|
||||
return DB.Transaction(func(tx *gorm.DB) error {
|
||||
if err := ValidateAuthSessionWithTx(tx, identity); err != nil {
|
||||
return err
|
||||
}
|
||||
var current User
|
||||
if err := tx.First(¤t, identity.UserID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
var binding UserOAuthBinding
|
||||
if err := tx.Where("user_id = ? AND provider_id = ?", identity.UserID, providerID).First(&binding).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return ErrAccountBindingChanged
|
||||
}
|
||||
return err
|
||||
}
|
||||
hasLogin := enabled.Password && current.Password != "" || enabled.WeChat && current.WeChatId != ""
|
||||
columns := map[string]string{
|
||||
"github_id": current.GitHubId, "discord_id": current.DiscordId,
|
||||
"oidc_id": current.OidcId, "linux_do_id": current.LinuxDOId, "telegram_id": current.TelegramId,
|
||||
}
|
||||
for _, column := range enabled.OAuthColumns {
|
||||
hasLogin = hasLogin || columns[column] != ""
|
||||
}
|
||||
if !hasLogin && enabled.Passkey {
|
||||
var count int64
|
||||
if err := tx.Model(&PasskeyCredential{}).Where("user_id = ?", identity.UserID).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
hasLogin = count > 0
|
||||
}
|
||||
if !hasLogin && len(enabled.CustomProviderIDs) > 0 {
|
||||
var count int64
|
||||
if err := tx.Model(&UserOAuthBinding{}).Where("user_id = ? AND provider_id <> ? AND provider_id IN ?", identity.UserID, providerID, enabled.CustomProviderIDs).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
hasLogin = count > 0
|
||||
}
|
||||
if !hasLogin {
|
||||
return ErrLastLoginMethod
|
||||
}
|
||||
return tx.Delete(&binding).Error
|
||||
})
|
||||
}
|
||||
|
||||
func UpdateUserBindColumnForSessionWithTx(tx *gorm.DB, identity AuthSessionIdentity, column, value string) error {
|
||||
if !userBindColumns[column] || value == "" {
|
||||
return ErrAccountBindingChanged
|
||||
}
|
||||
if err := ValidateAuthSessionWithTx(tx, identity); err != nil {
|
||||
return err
|
||||
}
|
||||
// Preserve the existing ownership check within the transaction and update
|
||||
// only the chosen binding column, never a complete user snapshot.
|
||||
var count int64
|
||||
if err := tx.Model(&User{}).Where(column+" = ? AND id <> ?", value, identity.UserID).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count != 0 {
|
||||
return ErrExternalIdentityAlreadyClaimed
|
||||
}
|
||||
return tx.Model(&User{}).Where("id = ?", identity.UserID).Update(column, value).Error
|
||||
}
|
||||
|
||||
func UpdateUserOAuthBindingForSessionWithTx(tx *gorm.DB, identity AuthSessionIdentity, providerID int, subject string) error {
|
||||
if providerID <= 0 || subject == "" || len(subject) > 256 {
|
||||
return ErrAccountBindingChanged
|
||||
}
|
||||
if err := ValidateAuthSessionWithTx(tx, identity); err != nil {
|
||||
return err
|
||||
}
|
||||
var binding UserOAuthBinding
|
||||
err := tx.Where("user_id = ? AND provider_id = ?", identity.UserID, providerID).First(&binding).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return CreateUserOAuthBindingWithTx(tx, &UserOAuthBinding{UserId: identity.UserID, ProviderId: providerID, ProviderUserId: subject})
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// The existing unique provider/subject index rejects concurrent ownership.
|
||||
return tx.Model(&binding).Update("provider_user_id", subject).Error
|
||||
}
|
||||
Reference in new issue
Block a user