[#6607] allowed manually changing the password from the create/update hooks

This commit is contained in:
Gani Georgiev 2025-04-05 13:35:02 +03:00
parent e49025c8e5
commit 201e2ccb05
3 changed files with 148 additions and 8 deletions

View File

@ -31,7 +31,7 @@
- Soft-deprecated the `$http.send`'s `result.raw` field in favor of `result.body` that contains the response body as plain bytes slice to avoid the discrepancies between Go and the JSVM when casting binary data to string.
(@todo update docs to use the new field)
- Minor UI fixes (_removed the superuser fields from the auth record create/update body examples, etc._).
- Other minor improvements (_removed the superuser fields from the auth record create/update body examples, allowed programmatically updating the auth record password from the create/update hooks, etc._).
## v0.26.6

View File

@ -27,9 +27,10 @@ type RecordUpsert struct {
accessLevel int
// extra password fields
password string
passwordConfirm string
oldPassword string
disablePasswordValidations bool
password string
passwordConfirm string
oldPassword string
}
// NewRecordUpsert creates a new [RecordUpsert] form from the provided [core.App] and [core.Record] instances
@ -130,6 +131,8 @@ func (form *RecordUpsert) validateFormFields() error {
return nil
}
form.syncPasswordFields()
isNew := form.record.IsNew()
original := form.record.Original()
@ -165,17 +168,17 @@ func (form *RecordUpsert) validateFormFields() error {
validation.Key(
"password",
validation.When(
(isNew || form.passwordConfirm != "" || form.oldPassword != ""),
!form.disablePasswordValidations && (isNew || form.passwordConfirm != "" || form.oldPassword != ""),
validation.Required,
),
),
validation.Key(
"passwordConfirm",
validation.When(
(isNew || form.password != "" || form.oldPassword != ""),
!form.disablePasswordValidations && (isNew || form.password != "" || form.oldPassword != ""),
validation.Required,
),
validation.By(validators.Equal(form.password)),
validation.When(!form.disablePasswordValidations, validation.By(validators.Equal(form.password))),
),
validation.Key(
"oldPassword",
@ -183,7 +186,7 @@ func (form *RecordUpsert) validateFormFields() error {
// - form.HasManageAccess() is not satisfied
// - changing the existing password
validation.When(
!isNew && !form.HasManageAccess() && (form.password != "" || form.passwordConfirm != ""),
!form.disablePasswordValidations && !isNew && !form.HasManageAccess() && (form.password != "" || form.passwordConfirm != ""),
validation.Required,
validation.By(form.checkOldPassword),
),
@ -287,3 +290,27 @@ func (form *RecordUpsert) Submit() error {
// run record validations and persist in db
return form.app.SaveWithContext(form.ctx, form.record)
}
// syncPasswordFields syncs the form's auth password fields with their
// corresponding record field values.
//
// This could be useful in case the password fields were programmatically set
// directly by modifying the related record model.
func (form *RecordUpsert) syncPasswordFields() {
if !form.record.Collection().IsAuth() {
return // not an auth collection
}
form.disablePasswordValidations = false
rawPassword := form.record.GetRaw(core.FieldNamePassword)
if v, ok := rawPassword.(*core.PasswordFieldValue); ok && v != nil {
if
// programmatically set custom plain password value
(v.Plain != "" && v.Plain != form.password) ||
// random generated password for new record
(v.Plain == "" && v.Hash != "" && form.record.IsNew()) {
form.disablePasswordValidations = true
}
}
}

View File

@ -887,6 +887,119 @@ func TestRecordUpsertSubmitSuccess(t *testing.T) {
testFilesCount(t, testApp, record, 2) // the file + attrs
}
func TestRecordUpsertPasswordsSync(t *testing.T) {
testApp, _ := tests.NewTestApp()
defer testApp.Cleanup()
users, err := testApp.FindCollectionByNameOrId("users")
if err != nil {
t.Fatal(err)
}
t.Run("new user without password", func(t *testing.T) {
record := core.NewRecord(users)
form := forms.NewRecordUpsert(testApp, record)
err := form.Submit()
tests.TestValidationErrors(t, err, []string{"password", "passwordConfirm"})
})
t.Run("new user with manual password", func(t *testing.T) {
record := core.NewRecord(users)
form := forms.NewRecordUpsert(testApp, record)
record.SetPassword("1234567890")
err := form.Submit()
if err != nil {
t.Fatalf("Expected no errors, got %v", err)
}
})
t.Run("new user with random password", func(t *testing.T) {
record := core.NewRecord(users)
form := forms.NewRecordUpsert(testApp, record)
record.SetRandomPassword()
err := form.Submit()
if err != nil {
t.Fatalf("Expected no errors, got %v", err)
}
})
t.Run("update user with no password change", func(t *testing.T) {
record, err := testApp.FindAuthRecordByEmail(users, "test@example.com")
if err != nil {
t.Fatal(err)
}
oldHash := record.GetString("password:hash")
form := forms.NewRecordUpsert(testApp, record)
err = form.Submit()
if err != nil {
t.Fatalf("Expected no errors, got %v", err)
}
newHash := record.GetString("password:hash")
if newHash == "" || newHash != oldHash {
t.Fatal("Expected no password change")
}
})
t.Run("update user with manual password change", func(t *testing.T) {
record, err := testApp.FindAuthRecordByEmail(users, "test@example.com")
if err != nil {
t.Fatal(err)
}
oldHash := record.GetString("password:hash")
form := forms.NewRecordUpsert(testApp, record)
record.SetPassword("1234567890")
err = form.Submit()
if err != nil {
t.Fatalf("Expected no errors, got %v", err)
}
newHash := record.GetString("password:hash")
if newHash == "" || newHash == oldHash {
t.Fatal("Expected password change")
}
})
t.Run("update user with random password change", func(t *testing.T) {
record, err := testApp.FindAuthRecordByEmail(users, "test@example.com")
if err != nil {
t.Fatal(err)
}
oldHash := record.GetString("password:hash")
form := forms.NewRecordUpsert(testApp, record)
record.SetRandomPassword()
err = form.Submit()
if err != nil {
t.Fatalf("Expected no errors, got %v", err)
}
newHash := record.GetString("password:hash")
if newHash == "" || newHash == oldHash {
t.Fatal("Expected password change")
}
})
}
// -------------------------------------------------------------------
func testFilesCount(t *testing.T, app core.App, record *core.Record, count int) {