From 167e23e9ba359e2b660aadaf6e73b2e4931b1691 Mon Sep 17 00:00:00 2001 From: Gani Georgiev Date: Wed, 27 May 2026 09:51:43 +0300 Subject: [PATCH] added extra confirm verification guards --- apis/record_auth_verification_confirm.go | 7 ++ apis/record_auth_verification_confirm_test.go | 81 ++++++++++++++++++- 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/apis/record_auth_verification_confirm.go b/apis/record_auth_verification_confirm.go index dcf86fb2..c211a6e6 100644 --- a/apis/record_auth_verification_confirm.go +++ b/apis/record_auth_verification_confirm.go @@ -45,6 +45,13 @@ func recordConfirmVerification(e *core.RequestEvent) error { if !wasVerified { e.Record.SetVerified(true) + // similar to the OTP auth, we enforce an extra password reset + // guard as this way is less prone to pre-hijacking attacks + // in case the password auth is eventually enabled later + if !e.Record.Collection().PasswordAuth.Enabled { + e.Record.SetRandomPassword() + } + if err := e.App.Save(e.Record); err != nil { return firstApiError(err, e.BadRequestError("An error occurred while saving the verified state.", err)) } diff --git a/apis/record_auth_verification_confirm_test.go b/apis/record_auth_verification_confirm_test.go index d291b056..e24ee8f2 100644 --- a/apis/record_auth_verification_confirm_test.go +++ b/apis/record_auth_verification_confirm_test.go @@ -120,7 +120,7 @@ func TestRecordConfirmVerification(t *testing.T) { } if user.Verified() { - t.Fatalf("Expected the user to be unverified before the confirmation") + t.Fatal("Expected the user to be unverified before the confirmation") } // ensure that there is at least one pre-existing OAuth2 link @@ -152,6 +152,85 @@ func TestRecordConfirmVerification(t *testing.T) { } }, }, + { + Name: "valid token (disabled password auth)", + Method: http.MethodPost, + URL: "/api/collections/users/confirm-verification", + Body: strings.NewReader(`{ + "token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsImV4cCI6MjUyNDYwNDQ2MSwidHlwZSI6InZlcmlmaWNhdGlvbiIsImNvbGxlY3Rpb25JZCI6Il9wYl91c2Vyc19hdXRoXyIsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSJ9.SetHpu2H-x-q4TIUz-xiQjwi7MNwLCLvSs4O0hUSp0E" + }`), + ExpectedStatus: 204, + ExpectedEvents: map[string]int{ + "*": 0, + "OnRecordConfirmVerificationRequest": 1, + "OnModelUpdate": 1, + "OnModelValidate": 1, + "OnModelUpdateExecute": 1, + "OnModelAfterUpdateSuccess": 1, + "OnRecordUpdate": 1, + "OnRecordValidate": 1, + "OnRecordUpdateExecute": 1, + "OnRecordAfterUpdateSuccess": 1, + // unverified->verified external auths removal + "OnModelDelete": 2, + "OnModelDeleteExecute": 2, + "OnModelAfterDeleteSuccess": 2, + "OnRecordDelete": 2, + "OnRecordDeleteExecute": 2, + "OnRecordAfterDeleteSuccess": 2, + }, + BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) { + user, err := app.FindAuthRecordByEmail("users", "test@example.com") + if err != nil { + t.Fatal(err) + } + + user.Collection().PasswordAuth.Enabled = false + if err = app.Save(user.Collection()); err != nil { + t.Fatal(err) + } + + if user.Verified() { + t.Fatal("Expected the user to be unverified before the confirmation") + } + + if !user.ValidatePassword("1234567890") { + t.Fatal("Expected password to be valid") + } + + // ensure that there is at least one pre-existing OAuth2 link + externalAuths, err := app.FindAllExternalAuthsByRecord(user) + if err != nil { + t.Fatal(err) + } + if len(externalAuths) == 0 { + t.Fatal("Expected at least one external auths") + } + }, + AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) { + user, err := app.FindAuthRecordByEmail("users", "test@example.com") + if err != nil { + t.Fatal(err) + } + + if !user.Verified() { + t.Fatalf("Expected the user to be verified after the confirmation") + } + + if user.ValidatePassword("1234567890") { + t.Fatal("Expected the user password to be reset") + } + + // ensure that all pre-existing OAuth2 links are cleared + externalAuths, err := app.FindAllExternalAuthsByRecord(user) + if err != nil { + t.Fatal(err) + } + if len(externalAuths) > 0 { + t.Fatalf("Expected all external auths to be cleared, found %d", len(externalAuths)) + } + }, + }, { Name: "valid token (already verified)", Method: http.MethodPost,