mirror of
https://github.com/pocketbase/pocketbase.git
synced 2025-03-20 22:36:00 +02:00
[#5964] refresh the token key on email change
This commit is contained in:
parent
0d720c3c9d
commit
76b9051011
@ -10,6 +10,8 @@
|
|||||||
|
|
||||||
- Added `mailer.Message.InlineAttachments` field for attaching inline files to an email (_aka. `cid` links_).
|
- Added `mailer.Message.InlineAttachments` field for attaching inline files to an email (_aka. `cid` links_).
|
||||||
|
|
||||||
|
- Invalidate all record tokens when the auth record email is changed programmatically or by a superuser ([#5964](https://github.com/pocketbase/pocketbase/issues/5964)).
|
||||||
|
|
||||||
- ⚠️ Removed the "dry submit" when executing the collections Create API rule
|
- ⚠️ Removed the "dry submit" when executing the collections Create API rule
|
||||||
(you can find more details why this change was introduced and how it could affect your app in https://github.com/pocketbase/pocketbase/discussions/6073).
|
(you can find more details why this change was introduced and how it could affect your app in https://github.com/pocketbase/pocketbase/discussions/6073).
|
||||||
For most users it should be non-breaking change, BUT if you have Create API rules that uses self-references or view counters you may have to adjust them manually.
|
For most users it should be non-breaking change, BUT if you have Create API rules that uses self-references or view counters you may have to adjust them manually.
|
||||||
|
@ -38,9 +38,8 @@ func recordConfirmEmailChange(e *core.RequestEvent) error {
|
|||||||
event.NewEmail = newEmail
|
event.NewEmail = newEmail
|
||||||
|
|
||||||
return e.App.OnRecordConfirmEmailChangeRequest().Trigger(event, func(e *core.RecordConfirmEmailChangeRequestEvent) error {
|
return e.App.OnRecordConfirmEmailChangeRequest().Trigger(event, func(e *core.RecordConfirmEmailChangeRequestEvent) error {
|
||||||
authRecord.Set(core.FieldNameEmail, e.NewEmail)
|
e.Record.SetEmail(e.NewEmail)
|
||||||
authRecord.Set(core.FieldNameVerified, true)
|
e.Record.SetVerified(true)
|
||||||
authRecord.RefreshTokenKey() // invalidate old tokens
|
|
||||||
|
|
||||||
if err := e.App.Save(e.Record); err != nil {
|
if err := e.App.Save(e.Record); err != nil {
|
||||||
return firstApiError(err, e.BadRequestError("Failed to confirm email change.", err))
|
return firstApiError(err, e.BadRequestError("Failed to confirm email change.", err))
|
||||||
|
@ -47,12 +47,12 @@ func recordConfirmPasswordReset(e *core.RequestEvent) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = form.app.Save(authRecord)
|
err = e.App.Save(authRecord)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return firstApiError(err, e.BadRequestError("Failed to set new password.", err))
|
return firstApiError(err, e.BadRequestError("Failed to set new password.", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
form.app.Store().Remove(getPasswordResetResendKey(authRecord))
|
e.App.Store().Remove(getPasswordResetResendKey(authRecord))
|
||||||
|
|
||||||
return e.NoContent(http.StatusNoContent)
|
return e.NoContent(http.StatusNoContent)
|
||||||
})
|
})
|
||||||
|
@ -186,11 +186,20 @@ func TestRecordConfirmPasswordReset(t *testing.T) {
|
|||||||
t.Fatal("Expected the user to be unverified")
|
t.Fatal("Expected the user to be unverified")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
oldTokenKey := user.TokenKey()
|
||||||
|
|
||||||
// manually change the email to check whether the verified state will be updated
|
// manually change the email to check whether the verified state will be updated
|
||||||
user.SetEmail("test_update@example.com")
|
user.SetEmail("test_update@example.com")
|
||||||
if err := app.Save(user); err != nil {
|
if err = app.Save(user); err != nil {
|
||||||
t.Fatalf("Failed to update user test email: %v", err)
|
t.Fatalf("Failed to update user test email: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resave with the old token key since the email change above
|
||||||
|
// would change it and will make the password token invalid
|
||||||
|
user.SetTokenKey(oldTokenKey)
|
||||||
|
if err = app.Save(user); err != nil {
|
||||||
|
t.Fatalf("Failed to restore original user tokenKey: %v", err)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) {
|
AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) {
|
||||||
_, err := app.FindAuthRecordByToken(
|
_, err := app.FindAuthRecordByToken(
|
||||||
|
@ -558,12 +558,21 @@ func TestRecordAuthWithOAuth2(t *testing.T) {
|
|||||||
t.Fatalf("Expected password %q to be valid", "1234567890")
|
t.Fatalf("Expected password %q to be valid", "1234567890")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
oldTokenKey := user.TokenKey()
|
||||||
|
|
||||||
// manually unset the user email
|
// manually unset the user email
|
||||||
user.SetEmail("")
|
user.SetEmail("")
|
||||||
if err := app.Save(user); err != nil {
|
if err = app.Save(user); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resave with the old token key since the email change above
|
||||||
|
// would change it and will make the password token invalid
|
||||||
|
user.SetTokenKey(oldTokenKey)
|
||||||
|
if err = app.Save(user); err != nil {
|
||||||
|
t.Fatalf("Failed to restore original user tokenKey: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// register the test provider
|
// register the test provider
|
||||||
auth.Providers["test"] = func() auth.Provider {
|
auth.Providers["test"] = func() auth.Provider {
|
||||||
return &oauth2MockProvider{
|
return &oauth2MockProvider{
|
||||||
|
@ -1413,13 +1413,19 @@ func onRecordValidate(e *RecordEvent) error {
|
|||||||
|
|
||||||
func onRecordSaveExecute(e *RecordEvent) error {
|
func onRecordSaveExecute(e *RecordEvent) error {
|
||||||
if e.Record.Collection().IsAuth() {
|
if e.Record.Collection().IsAuth() {
|
||||||
// ensure that the token key is different on password change
|
// ensure that the token key is regenerated on password change or email change
|
||||||
old := e.Record.Original()
|
if !e.Record.IsNew() {
|
||||||
if !e.Record.IsNew() &&
|
lastSavedRecord, err := e.App.FindRecordById(e.Record.Collection(), e.Record.Id)
|
||||||
old.TokenKey() == e.Record.TokenKey() &&
|
if err != nil {
|
||||||
old.Get(FieldNamePassword) != e.Record.Get(FieldNamePassword) {
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if lastSavedRecord.TokenKey() == e.Record.TokenKey() &&
|
||||||
|
(lastSavedRecord.Get(FieldNamePassword) != e.Record.Get(FieldNamePassword) ||
|
||||||
|
lastSavedRecord.Email() != e.Record.Email()) {
|
||||||
e.Record.RefreshTokenKey()
|
e.Record.RefreshTokenKey()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// cross-check that the auth record id is unique across all auth collections.
|
// cross-check that the auth record id is unique across all auth collections.
|
||||||
authCollections, err := e.App.FindAllCollections(CollectionTypeAuth)
|
authCollections, err := e.App.FindAllCollections(CollectionTypeAuth)
|
||||||
|
@ -1822,44 +1822,59 @@ func TestRecordSaveIdUpdateNoValidation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRecordSaveWithChangedPassword(t *testing.T) {
|
func TestRecordSaveWithAutoTokenKeyRefresh(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
app, _ := tests.NewTestApp()
|
app, _ := tests.NewTestApp()
|
||||||
defer app.Cleanup()
|
defer app.Cleanup()
|
||||||
|
|
||||||
record, err := app.FindAuthRecordByEmail("nologin", "test@example.com")
|
scenarios := []struct {
|
||||||
|
name string
|
||||||
|
payload map[string]any
|
||||||
|
expectedChange bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"no email or password change",
|
||||||
|
map[string]any{"name": "example"},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"password change",
|
||||||
|
map[string]any{"password": "1234567890"},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"email change",
|
||||||
|
map[string]any{"email": "test_update@example.com"},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range scenarios {
|
||||||
|
t.Run(s.name, func(t *testing.T) {
|
||||||
|
record, err := app.FindFirstRecordByFilter("nologin", "1=1")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
originalTokenKey := record.TokenKey()
|
originalTokenKey := record.TokenKey()
|
||||||
|
|
||||||
t.Run("no password change shouldn't change the tokenKey", func(t *testing.T) {
|
record.Load(s.payload)
|
||||||
record.Set("name", "example")
|
|
||||||
|
|
||||||
if err := app.Save(record); err != nil {
|
err = app.Save(record)
|
||||||
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenKey := record.TokenKey()
|
newTokenKey := record.TokenKey()
|
||||||
if tokenKey == "" || originalTokenKey != tokenKey {
|
|
||||||
t.Fatalf("Expected tokenKey to not change, got %q VS %q", originalTokenKey, tokenKey)
|
hasChange := originalTokenKey != newTokenKey
|
||||||
|
|
||||||
|
if hasChange != s.expectedChange {
|
||||||
|
t.Fatalf("Expected hasChange %v, got %v", s.expectedChange, hasChange)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("password change should change the tokenKey", func(t *testing.T) {
|
|
||||||
record.Set("password", "1234567890")
|
|
||||||
|
|
||||||
if err := app.Save(record); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenKey := record.TokenKey()
|
|
||||||
if tokenKey == "" || originalTokenKey == tokenKey {
|
|
||||||
t.Fatalf("Expected tokenKey to change, got %q VS %q", originalTokenKey, tokenKey)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRecordDelete(t *testing.T) {
|
func TestRecordDelete(t *testing.T) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user