[#4066] mark user as verified on confirm password reset
This commit is contained in:
parent
cd2fc536ca
commit
af7c6d8d9b
|
@ -2,6 +2,9 @@
|
||||||
|
|
||||||
- Added Bitbucket OAuth2 provider ([#3948](https://github.com/pocketbase/pocketbase/pull/3948); thanks @aabajyan).
|
- Added Bitbucket OAuth2 provider ([#3948](https://github.com/pocketbase/pocketbase/pull/3948); thanks @aabajyan).
|
||||||
|
|
||||||
|
- Mark user as verified on confirm password reset ([#4066](https://github.com/pocketbase/pocketbase/issues/4066)).
|
||||||
|
_If the user email has changed after issuing the reset token (eg. updated from the Admin UI), then the `verified` user state remains unchanged._
|
||||||
|
|
||||||
- Added `TestMailer.SentMessages` field that holds all sent test app emails until cleanup.
|
- Added `TestMailer.SentMessages` field that holds all sent test app emails until cleanup.
|
||||||
|
|
||||||
- Minor Admin UI improvements (reduced the min table row height, added new TinyMCE codesample languages, etc.)
|
- Minor Admin UI improvements (reduced the min table row height, added new TinyMCE codesample languages, etc.)
|
||||||
|
|
|
@ -644,7 +644,7 @@ func TestRecordAuthConfirmPasswordReset(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "valid token and data",
|
Name: "valid token and data (unverified user)",
|
||||||
Method: http.MethodPost,
|
Method: http.MethodPost,
|
||||||
Url: "/api/collections/users/confirm-password-reset",
|
Url: "/api/collections/users/confirm-password-reset",
|
||||||
Body: strings.NewReader(`{
|
Body: strings.NewReader(`{
|
||||||
|
@ -659,6 +659,132 @@ func TestRecordAuthConfirmPasswordReset(t *testing.T) {
|
||||||
"OnRecordBeforeConfirmPasswordResetRequest": 1,
|
"OnRecordBeforeConfirmPasswordResetRequest": 1,
|
||||||
"OnRecordAfterConfirmPasswordResetRequest": 1,
|
"OnRecordAfterConfirmPasswordResetRequest": 1,
|
||||||
},
|
},
|
||||||
|
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||||
|
user, err := app.Dao().FindAuthRecordByEmail("users", "test@example.com")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to fetch confirm password user: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Verified() {
|
||||||
|
t.Fatalf("Expected the user to be unverified")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
|
||||||
|
user, err := app.Dao().FindAuthRecordByToken(
|
||||||
|
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsImNvbGxlY3Rpb25JZCI6Il9wYl91c2Vyc19hdXRoXyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiZXhwIjoyMjA4OTg1MjYxfQ.R_4FOSUHIuJQ5Crl3PpIPCXMsoHzuTaNlccpXg_3FOg",
|
||||||
|
app.Settings().RecordPasswordResetToken.Secret,
|
||||||
|
)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Expected the password reset token to be invalidated")
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err = app.Dao().FindAuthRecordByEmail("users", "test@example.com")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to fetch confirm password user: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !user.Verified() {
|
||||||
|
t.Fatalf("Expected the user to be marked as verified")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "valid token and data (unverified user with different email from the one in the token)",
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Url: "/api/collections/users/confirm-password-reset",
|
||||||
|
Body: strings.NewReader(`{
|
||||||
|
"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsImNvbGxlY3Rpb25JZCI6Il9wYl91c2Vyc19hdXRoXyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiZXhwIjoyMjA4OTg1MjYxfQ.R_4FOSUHIuJQ5Crl3PpIPCXMsoHzuTaNlccpXg_3FOg",
|
||||||
|
"password":"12345678",
|
||||||
|
"passwordConfirm":"12345678"
|
||||||
|
}`),
|
||||||
|
ExpectedStatus: 204,
|
||||||
|
ExpectedEvents: map[string]int{
|
||||||
|
"OnModelAfterUpdate": 1,
|
||||||
|
"OnModelBeforeUpdate": 1,
|
||||||
|
"OnRecordBeforeConfirmPasswordResetRequest": 1,
|
||||||
|
"OnRecordAfterConfirmPasswordResetRequest": 1,
|
||||||
|
},
|
||||||
|
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||||
|
user, err := app.Dao().FindAuthRecordByEmail("users", "test@example.com")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to fetch confirm password user: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Verified() {
|
||||||
|
t.Fatalf("Expected the user to be unverified")
|
||||||
|
}
|
||||||
|
|
||||||
|
// manually change the email to check whether the verified state will be updated
|
||||||
|
user.SetEmail("test_update@example.com")
|
||||||
|
if err := app.Dao().WithoutHooks().SaveRecord(user); err != nil {
|
||||||
|
t.Fatalf("Failed to update user test email")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
|
||||||
|
user, err := app.Dao().FindAuthRecordByToken(
|
||||||
|
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsImNvbGxlY3Rpb25JZCI6Il9wYl91c2Vyc19hdXRoXyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiZXhwIjoyMjA4OTg1MjYxfQ.R_4FOSUHIuJQ5Crl3PpIPCXMsoHzuTaNlccpXg_3FOg",
|
||||||
|
app.Settings().RecordPasswordResetToken.Secret,
|
||||||
|
)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Expected the password reset token to be invalidated")
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err = app.Dao().FindAuthRecordByEmail("users", "test_update@example.com")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to fetch confirm password user: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Verified() {
|
||||||
|
t.Fatalf("Expected the user to remain unverified")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "valid token and data (verified user)",
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Url: "/api/collections/users/confirm-password-reset",
|
||||||
|
Body: strings.NewReader(`{
|
||||||
|
"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsImNvbGxlY3Rpb25JZCI6Il9wYl91c2Vyc19hdXRoXyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiZXhwIjoyMjA4OTg1MjYxfQ.R_4FOSUHIuJQ5Crl3PpIPCXMsoHzuTaNlccpXg_3FOg",
|
||||||
|
"password":"12345678",
|
||||||
|
"passwordConfirm":"12345678"
|
||||||
|
}`),
|
||||||
|
ExpectedStatus: 204,
|
||||||
|
ExpectedEvents: map[string]int{
|
||||||
|
"OnModelAfterUpdate": 1,
|
||||||
|
"OnModelBeforeUpdate": 1,
|
||||||
|
"OnRecordBeforeConfirmPasswordResetRequest": 1,
|
||||||
|
"OnRecordAfterConfirmPasswordResetRequest": 1,
|
||||||
|
},
|
||||||
|
BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
|
||||||
|
user, err := app.Dao().FindAuthRecordByEmail("users", "test@example.com")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to fetch confirm password user: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure that the user is already verified
|
||||||
|
user.SetVerified(true)
|
||||||
|
if err := app.Dao().WithoutHooks().SaveRecord(user); err != nil {
|
||||||
|
t.Fatalf("Failed to update user verified state")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
|
||||||
|
user, err := app.Dao().FindAuthRecordByToken(
|
||||||
|
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsImNvbGxlY3Rpb25JZCI6Il9wYl91c2Vyc19hdXRoXyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiZXhwIjoyMjA4OTg1MjYxfQ.R_4FOSUHIuJQ5Crl3PpIPCXMsoHzuTaNlccpXg_3FOg",
|
||||||
|
app.Settings().RecordPasswordResetToken.Secret,
|
||||||
|
)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Expected the password reset token to be invalidated")
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err = app.Dao().FindAuthRecordByEmail("users", "test@example.com")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to fetch confirm password user: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !user.Verified() {
|
||||||
|
t.Fatalf("Expected the user to remain verified")
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "OnRecordAfterConfirmPasswordResetRequest error response",
|
Name: "OnRecordAfterConfirmPasswordResetRequest error response",
|
||||||
|
|
|
@ -6,6 +6,8 @@ import (
|
||||||
"github.com/pocketbase/pocketbase/daos"
|
"github.com/pocketbase/pocketbase/daos"
|
||||||
"github.com/pocketbase/pocketbase/forms/validators"
|
"github.com/pocketbase/pocketbase/forms/validators"
|
||||||
"github.com/pocketbase/pocketbase/models"
|
"github.com/pocketbase/pocketbase/models"
|
||||||
|
"github.com/pocketbase/pocketbase/tools/security"
|
||||||
|
"github.com/spf13/cast"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RecordPasswordResetConfirm is an auth record password reset confirmation form.
|
// RecordPasswordResetConfirm is an auth record password reset confirmation form.
|
||||||
|
@ -91,9 +93,21 @@ func (form *RecordPasswordResetConfirm) Submit(interceptors ...InterceptorFunc[*
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !authRecord.Verified() {
|
||||||
|
payload, err := security.ParseUnverifiedJWT(form.Token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// mark as verified if the email hasn't changed
|
||||||
|
if authRecord.Email() == cast.ToString(payload["email"]) {
|
||||||
|
authRecord.SetVerified(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interceptorsErr := runInterceptors(authRecord, func(m *models.Record) error {
|
interceptorsErr := runInterceptors(authRecord, func(m *models.Record) error {
|
||||||
authRecord = m
|
authRecord = m
|
||||||
return form.dao.SaveRecord(m)
|
return form.dao.SaveRecord(authRecord)
|
||||||
}, interceptors...)
|
}, interceptors...)
|
||||||
|
|
||||||
if interceptorsErr != nil {
|
if interceptorsErr != nil {
|
||||||
|
|
Loading…
Reference in New Issue