[#1240] added dedicated before/after auth hooks and refactored the submit interceptors
This commit is contained in:
+23
-7
@@ -1,6 +1,7 @@
|
||||
package forms
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
|
||||
validation "github.com/go-ozzo/ozzo-validation/v4"
|
||||
@@ -46,19 +47,34 @@ func (form *AdminLogin) Validate() error {
|
||||
|
||||
// Submit validates and submits the admin form.
|
||||
// On success returns the authorized admin model.
|
||||
func (form *AdminLogin) Submit() (*models.Admin, error) {
|
||||
//
|
||||
// You can optionally provide a list of InterceptorFunc to
|
||||
// further modify the form behavior before persisting it.
|
||||
func (form *AdminLogin) Submit(interceptors ...InterceptorFunc[*models.Admin]) (*models.Admin, error) {
|
||||
if err := form.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
admin, err := form.dao.FindAdminByEmail(form.Identity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
admin, fetchErr := form.dao.FindAdminByEmail(form.Identity)
|
||||
|
||||
// ignore not found errors to allow custom fetch implementations
|
||||
if fetchErr != nil && !errors.Is(fetchErr, sql.ErrNoRows) {
|
||||
return nil, fetchErr
|
||||
}
|
||||
|
||||
if admin.ValidatePassword(form.Password) {
|
||||
return admin, nil
|
||||
interceptorsErr := runInterceptors(admin, func(m *models.Admin) error {
|
||||
admin = m
|
||||
|
||||
if admin == nil || !admin.ValidatePassword(form.Password) {
|
||||
return errors.New("Invalid login credentials.")
|
||||
}
|
||||
|
||||
return nil
|
||||
}, interceptors...)
|
||||
|
||||
if interceptorsErr != nil {
|
||||
return nil, interceptorsErr
|
||||
}
|
||||
|
||||
return nil, errors.New("Invalid login credentials.")
|
||||
return admin, nil
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package forms_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/pocketbase/pocketbase/forms"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"github.com/pocketbase/pocketbase/tests"
|
||||
)
|
||||
|
||||
@@ -47,3 +49,48 @@ func TestAdminLoginValidateAndSubmit(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminLoginInterceptors(t *testing.T) {
|
||||
testApp, _ := tests.NewTestApp()
|
||||
defer testApp.Cleanup()
|
||||
|
||||
form := forms.NewAdminLogin(testApp)
|
||||
form.Identity = "test@example.com"
|
||||
form.Password = "123456"
|
||||
var interceptorAdmin *models.Admin
|
||||
testErr := errors.New("test_error")
|
||||
|
||||
interceptor1Called := false
|
||||
interceptor1 := func(next forms.InterceptorNextFunc[*models.Admin]) forms.InterceptorNextFunc[*models.Admin] {
|
||||
return func(admin *models.Admin) error {
|
||||
interceptor1Called = true
|
||||
return next(admin)
|
||||
}
|
||||
}
|
||||
|
||||
interceptor2Called := false
|
||||
interceptor2 := func(next forms.InterceptorNextFunc[*models.Admin]) forms.InterceptorNextFunc[*models.Admin] {
|
||||
return func(admin *models.Admin) error {
|
||||
interceptorAdmin = admin
|
||||
interceptor2Called = true
|
||||
return testErr
|
||||
}
|
||||
}
|
||||
|
||||
_, submitErr := form.Submit(interceptor1, interceptor2)
|
||||
if submitErr != testErr {
|
||||
t.Fatalf("Expected submitError %v, got %v", testErr, submitErr)
|
||||
}
|
||||
|
||||
if !interceptor1Called {
|
||||
t.Fatalf("Expected interceptor1 to be called")
|
||||
}
|
||||
|
||||
if !interceptor2Called {
|
||||
t.Fatalf("Expected interceptor2 to be called")
|
||||
}
|
||||
|
||||
if interceptorAdmin == nil || interceptorAdmin.Email != form.Identity {
|
||||
t.Fatalf("Expected Admin model with email %s, got %v", form.Identity, interceptorAdmin)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,10 @@ func (form *AdminPasswordResetConfirm) checkToken(value any) error {
|
||||
|
||||
// Submit validates and submits the admin password reset confirmation form.
|
||||
// On success returns the updated admin model associated to `form.Token`.
|
||||
func (form *AdminPasswordResetConfirm) Submit() (*models.Admin, error) {
|
||||
//
|
||||
// You can optionally provide a list of InterceptorFunc to further
|
||||
// modify the form behavior before persisting it.
|
||||
func (form *AdminPasswordResetConfirm) Submit(interceptors ...InterceptorFunc[*models.Admin]) (*models.Admin, error) {
|
||||
if err := form.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -80,8 +83,13 @@ func (form *AdminPasswordResetConfirm) Submit() (*models.Admin, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := form.dao.SaveAdmin(admin); err != nil {
|
||||
return nil, err
|
||||
interceptorsErr := runInterceptors(admin, func(m *models.Admin) error {
|
||||
admin = m
|
||||
return form.dao.SaveAdmin(m)
|
||||
}, interceptors...)
|
||||
|
||||
if interceptorsErr != nil {
|
||||
return nil, interceptorsErr
|
||||
}
|
||||
|
||||
return admin, nil
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package forms_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/pocketbase/pocketbase/forms"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"github.com/pocketbase/pocketbase/tests"
|
||||
"github.com/pocketbase/pocketbase/tools/security"
|
||||
)
|
||||
@@ -54,7 +56,24 @@ func TestAdminPasswordResetConfirmValidateAndSubmit(t *testing.T) {
|
||||
form.Password = s.password
|
||||
form.PasswordConfirm = s.passwordConfirm
|
||||
|
||||
admin, err := form.Submit()
|
||||
interceptorCalls := 0
|
||||
interceptor := func(next forms.InterceptorNextFunc[*models.Admin]) forms.InterceptorNextFunc[*models.Admin] {
|
||||
return func(m *models.Admin) error {
|
||||
interceptorCalls++
|
||||
return next(m)
|
||||
}
|
||||
}
|
||||
|
||||
admin, err := form.Submit(interceptor)
|
||||
|
||||
// check interceptor calls
|
||||
expectInterceptorCalls := 1
|
||||
if s.expectError {
|
||||
expectInterceptorCalls = 0
|
||||
}
|
||||
if interceptorCalls != expectInterceptorCalls {
|
||||
t.Errorf("[%d] Expected interceptor to be called %d, got %d", i, expectInterceptorCalls, interceptorCalls)
|
||||
}
|
||||
|
||||
hasErr := err != nil
|
||||
if hasErr != s.expectError {
|
||||
@@ -78,3 +97,54 @@ func TestAdminPasswordResetConfirmValidateAndSubmit(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminPasswordResetConfirmInterceptors(t *testing.T) {
|
||||
testApp, _ := tests.NewTestApp()
|
||||
defer testApp.Cleanup()
|
||||
|
||||
admin, err := testApp.Dao().FindAdminByEmail("test@example.com")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
form := forms.NewAdminPasswordResetConfirm(testApp)
|
||||
form.Token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsImV4cCI6MjIwODk4MTYwMH0.kwFEler6KSMKJNstuaSDvE1QnNdCta5qSnjaIQ0hhhc"
|
||||
form.Password = "1234567891"
|
||||
form.PasswordConfirm = "1234567891"
|
||||
interceptorTokenKey := admin.TokenKey
|
||||
testErr := errors.New("test_error")
|
||||
|
||||
interceptor1Called := false
|
||||
interceptor1 := func(next forms.InterceptorNextFunc[*models.Admin]) forms.InterceptorNextFunc[*models.Admin] {
|
||||
return func(admin *models.Admin) error {
|
||||
interceptor1Called = true
|
||||
return next(admin)
|
||||
}
|
||||
}
|
||||
|
||||
interceptor2Called := false
|
||||
interceptor2 := func(next forms.InterceptorNextFunc[*models.Admin]) forms.InterceptorNextFunc[*models.Admin] {
|
||||
return func(admin *models.Admin) error {
|
||||
interceptorTokenKey = admin.TokenKey
|
||||
interceptor2Called = true
|
||||
return testErr
|
||||
}
|
||||
}
|
||||
|
||||
_, submitErr := form.Submit(interceptor1, interceptor2)
|
||||
if submitErr != testErr {
|
||||
t.Fatalf("Expected submitError %v, got %v", testErr, submitErr)
|
||||
}
|
||||
|
||||
if !interceptor1Called {
|
||||
t.Fatalf("Expected interceptor1 to be called")
|
||||
}
|
||||
|
||||
if !interceptor2Called {
|
||||
t.Fatalf("Expected interceptor2 to be called")
|
||||
}
|
||||
|
||||
if interceptorTokenKey == admin.TokenKey {
|
||||
t.Fatalf("Expected the form model to be filled before calling the interceptors")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/pocketbase/pocketbase/daos"
|
||||
"github.com/pocketbase/pocketbase/mails"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"github.com/pocketbase/pocketbase/tools/types"
|
||||
)
|
||||
|
||||
@@ -55,7 +56,10 @@ func (form *AdminPasswordResetRequest) Validate() error {
|
||||
|
||||
// Submit validates and submits the form.
|
||||
// On success sends a password reset email to the `form.Email` admin.
|
||||
func (form *AdminPasswordResetRequest) Submit() error {
|
||||
//
|
||||
// You can optionally provide a list of InterceptorFunc to further
|
||||
// modify the form behavior before persisting it.
|
||||
func (form *AdminPasswordResetRequest) Submit(interceptors ...InterceptorFunc[*models.Admin]) error {
|
||||
if err := form.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -71,12 +75,14 @@ func (form *AdminPasswordResetRequest) Submit() error {
|
||||
return errors.New("You have already requested a password reset.")
|
||||
}
|
||||
|
||||
if err := mails.SendAdminPasswordReset(form.app, admin); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// update last sent timestamp
|
||||
admin.LastResetSentAt = types.NowDateTime()
|
||||
|
||||
return form.dao.SaveAdmin(admin)
|
||||
return runInterceptors(admin, func(m *models.Admin) error {
|
||||
if err := mails.SendAdminPasswordReset(form.app, m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return form.dao.SaveAdmin(m)
|
||||
}, interceptors...)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package forms_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/pocketbase/pocketbase/forms"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"github.com/pocketbase/pocketbase/tests"
|
||||
)
|
||||
|
||||
@@ -31,7 +33,24 @@ func TestAdminPasswordResetRequestValidateAndSubmit(t *testing.T) {
|
||||
|
||||
adminBefore, _ := testApp.Dao().FindAdminByEmail(s.email)
|
||||
|
||||
err := form.Submit()
|
||||
interceptorCalls := 0
|
||||
interceptor := func(next forms.InterceptorNextFunc[*models.Admin]) forms.InterceptorNextFunc[*models.Admin] {
|
||||
return func(m *models.Admin) error {
|
||||
interceptorCalls++
|
||||
return next(m)
|
||||
}
|
||||
}
|
||||
|
||||
err := form.Submit(interceptor)
|
||||
|
||||
// check interceptor calls
|
||||
expectInterceptorCalls := 1
|
||||
if s.expectError {
|
||||
expectInterceptorCalls = 0
|
||||
}
|
||||
if interceptorCalls != expectInterceptorCalls {
|
||||
t.Errorf("[%d] Expected interceptor to be called %d, got %d", i, expectInterceptorCalls, interceptorCalls)
|
||||
}
|
||||
|
||||
hasErr := err != nil
|
||||
if hasErr != s.expectError {
|
||||
@@ -53,3 +72,52 @@ func TestAdminPasswordResetRequestValidateAndSubmit(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminPasswordResetRequestInterceptors(t *testing.T) {
|
||||
testApp, _ := tests.NewTestApp()
|
||||
defer testApp.Cleanup()
|
||||
|
||||
admin, err := testApp.Dao().FindAdminByEmail("test@example.com")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
form := forms.NewAdminPasswordResetRequest(testApp)
|
||||
form.Email = admin.Email
|
||||
interceptorLastResetSentAt := admin.LastResetSentAt
|
||||
testErr := errors.New("test_error")
|
||||
|
||||
interceptor1Called := false
|
||||
interceptor1 := func(next forms.InterceptorNextFunc[*models.Admin]) forms.InterceptorNextFunc[*models.Admin] {
|
||||
return func(admin *models.Admin) error {
|
||||
interceptor1Called = true
|
||||
return next(admin)
|
||||
}
|
||||
}
|
||||
|
||||
interceptor2Called := false
|
||||
interceptor2 := func(next forms.InterceptorNextFunc[*models.Admin]) forms.InterceptorNextFunc[*models.Admin] {
|
||||
return func(admin *models.Admin) error {
|
||||
interceptorLastResetSentAt = admin.LastResetSentAt
|
||||
interceptor2Called = true
|
||||
return testErr
|
||||
}
|
||||
}
|
||||
|
||||
submitErr := form.Submit(interceptor1, interceptor2)
|
||||
if submitErr != testErr {
|
||||
t.Fatalf("Expected submitError %v, got %v", testErr, submitErr)
|
||||
}
|
||||
|
||||
if !interceptor1Called {
|
||||
t.Fatalf("Expected interceptor1 to be called")
|
||||
}
|
||||
|
||||
if !interceptor2Called {
|
||||
t.Fatalf("Expected interceptor2 to be called")
|
||||
}
|
||||
|
||||
if interceptorLastResetSentAt.String() == admin.LastResetSentAt.String() {
|
||||
t.Fatalf("Expected the form model to be filled before calling the interceptors")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ func (form *AdminUpsert) checkUniqueEmail(value any) error {
|
||||
//
|
||||
// You can optionally provide a list of InterceptorFunc to further
|
||||
// modify the form behavior before persisting it.
|
||||
func (form *AdminUpsert) Submit(interceptors ...InterceptorFunc) error {
|
||||
func (form *AdminUpsert) Submit(interceptors ...InterceptorFunc[*models.Admin]) error {
|
||||
if err := form.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -117,7 +117,7 @@ func (form *AdminUpsert) Submit(interceptors ...InterceptorFunc) error {
|
||||
form.admin.SetPassword(form.Password)
|
||||
}
|
||||
|
||||
return runInterceptors(func() error {
|
||||
return form.dao.SaveAdmin(form.admin)
|
||||
return runInterceptors(form.admin, func(admin *models.Admin) error {
|
||||
return form.dao.SaveAdmin(admin)
|
||||
}, interceptors...)
|
||||
}
|
||||
|
||||
@@ -137,10 +137,10 @@ func TestAdminUpsertValidateAndSubmit(t *testing.T) {
|
||||
|
||||
interceptorCalls := 0
|
||||
|
||||
err := form.Submit(func(next forms.InterceptorNextFunc) forms.InterceptorNextFunc {
|
||||
return func() error {
|
||||
err := form.Submit(func(next forms.InterceptorNextFunc[*models.Admin]) forms.InterceptorNextFunc[*models.Admin] {
|
||||
return func(m *models.Admin) error {
|
||||
interceptorCalls++
|
||||
return next()
|
||||
return next(m)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -196,16 +196,16 @@ func TestAdminUpsertSubmitInterceptors(t *testing.T) {
|
||||
interceptorAdminEmail := ""
|
||||
|
||||
interceptor1Called := false
|
||||
interceptor1 := func(next forms.InterceptorNextFunc) forms.InterceptorNextFunc {
|
||||
return func() error {
|
||||
interceptor1 := func(next forms.InterceptorNextFunc[*models.Admin]) forms.InterceptorNextFunc[*models.Admin] {
|
||||
return func(m *models.Admin) error {
|
||||
interceptor1Called = true
|
||||
return next()
|
||||
return next(m)
|
||||
}
|
||||
}
|
||||
|
||||
interceptor2Called := false
|
||||
interceptor2 := func(next forms.InterceptorNextFunc) forms.InterceptorNextFunc {
|
||||
return func() error {
|
||||
interceptor2 := func(next forms.InterceptorNextFunc[*models.Admin]) forms.InterceptorNextFunc[*models.Admin] {
|
||||
return func(m *models.Admin) error {
|
||||
interceptorAdminEmail = admin.Email // to check if the record was filled
|
||||
interceptor2Called = true
|
||||
return testErr
|
||||
|
||||
+9
-22
@@ -4,8 +4,6 @@ package forms
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
)
|
||||
|
||||
// base ID value regex pattern
|
||||
@@ -13,32 +11,21 @@ var idRegex = regexp.MustCompile(`^[^\@\#\$\&\|\.\,\'\"\\\/\s]+$`)
|
||||
|
||||
// InterceptorNextFunc is a interceptor handler function.
|
||||
// Usually used in combination with InterceptorFunc.
|
||||
type InterceptorNextFunc = func() error
|
||||
type InterceptorNextFunc[T any] func(t T) error
|
||||
|
||||
// InterceptorFunc defines a single interceptor function that
|
||||
// will execute the provided next func handler.
|
||||
type InterceptorFunc func(next InterceptorNextFunc) InterceptorNextFunc
|
||||
type InterceptorFunc[T any] func(next InterceptorNextFunc[T]) InterceptorNextFunc[T]
|
||||
|
||||
// runInterceptors executes the provided list of interceptors.
|
||||
func runInterceptors(next InterceptorNextFunc, interceptors ...InterceptorFunc) error {
|
||||
func runInterceptors[T any](
|
||||
data T,
|
||||
next InterceptorNextFunc[T],
|
||||
interceptors ...InterceptorFunc[T],
|
||||
) error {
|
||||
for i := len(interceptors) - 1; i >= 0; i-- {
|
||||
next = interceptors[i](next)
|
||||
}
|
||||
return next()
|
||||
}
|
||||
|
||||
// InterceptorWithRecordNextFunc is a Record interceptor handler function.
|
||||
// Usually used in combination with InterceptorWithRecordFunc.
|
||||
type InterceptorWithRecordNextFunc = func(record *models.Record) error
|
||||
|
||||
// InterceptorWithRecordFunc defines a single Record interceptor function
|
||||
// that will execute the provided next func handler.
|
||||
type InterceptorWithRecordFunc func(next InterceptorWithRecordNextFunc) InterceptorWithRecordNextFunc
|
||||
|
||||
// runInterceptorsWithRecord executes the provided list of Record interceptors.
|
||||
func runInterceptorsWithRecord(record *models.Record, next InterceptorWithRecordNextFunc, interceptors ...InterceptorWithRecordFunc) error {
|
||||
for i := len(interceptors) - 1; i >= 0; i-- {
|
||||
next = interceptors[i](next)
|
||||
}
|
||||
return next(record)
|
||||
|
||||
return next(data)
|
||||
}
|
||||
|
||||
@@ -345,7 +345,7 @@ func (form *CollectionUpsert) checkOptions(value any) error {
|
||||
//
|
||||
// You can optionally provide a list of InterceptorFunc to further
|
||||
// modify the form behavior before persisting it.
|
||||
func (form *CollectionUpsert) Submit(interceptors ...InterceptorFunc) error {
|
||||
func (form *CollectionUpsert) Submit(interceptors ...InterceptorFunc[*models.Collection]) error {
|
||||
if err := form.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -377,7 +377,7 @@ func (form *CollectionUpsert) Submit(interceptors ...InterceptorFunc) error {
|
||||
form.collection.DeleteRule = form.DeleteRule
|
||||
form.collection.SetOptions(form.Options)
|
||||
|
||||
return runInterceptors(func() error {
|
||||
return form.dao.SaveCollection(form.collection)
|
||||
return runInterceptors(form.collection, func(collection *models.Collection) error {
|
||||
return form.dao.SaveCollection(collection)
|
||||
}, interceptors...)
|
||||
}
|
||||
|
||||
@@ -351,10 +351,10 @@ func TestCollectionUpsertValidateAndSubmit(t *testing.T) {
|
||||
}
|
||||
|
||||
interceptorCalls := 0
|
||||
interceptor := func(next forms.InterceptorNextFunc) forms.InterceptorNextFunc {
|
||||
return func() error {
|
||||
interceptor := func(next forms.InterceptorNextFunc[*models.Collection]) forms.InterceptorNextFunc[*models.Collection] {
|
||||
return func(c *models.Collection) error {
|
||||
interceptorCalls++
|
||||
return next()
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -451,16 +451,16 @@ func TestCollectionUpsertSubmitInterceptors(t *testing.T) {
|
||||
interceptorCollectionName := ""
|
||||
|
||||
interceptor1Called := false
|
||||
interceptor1 := func(next forms.InterceptorNextFunc) forms.InterceptorNextFunc {
|
||||
return func() error {
|
||||
interceptor1 := func(next forms.InterceptorNextFunc[*models.Collection]) forms.InterceptorNextFunc[*models.Collection] {
|
||||
return func(c *models.Collection) error {
|
||||
interceptor1Called = true
|
||||
return next()
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
|
||||
interceptor2Called := false
|
||||
interceptor2 := func(next forms.InterceptorNextFunc) forms.InterceptorNextFunc {
|
||||
return func() error {
|
||||
interceptor2 := func(next forms.InterceptorNextFunc[*models.Collection]) forms.InterceptorNextFunc[*models.Collection] {
|
||||
return func(c *models.Collection) error {
|
||||
interceptorCollectionName = collection.Name // to check if the record was filled
|
||||
interceptor2Called = true
|
||||
return testErr
|
||||
|
||||
@@ -56,15 +56,15 @@ func (form *CollectionsImport) Validate() error {
|
||||
//
|
||||
// You can optionally provide a list of InterceptorFunc to further
|
||||
// modify the form behavior before persisting it.
|
||||
func (form *CollectionsImport) Submit(interceptors ...InterceptorFunc) error {
|
||||
func (form *CollectionsImport) Submit(interceptors ...InterceptorFunc[[]*models.Collection]) error {
|
||||
if err := form.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return runInterceptors(func() error {
|
||||
return runInterceptors(form.Collections, func(collections []*models.Collection) error {
|
||||
return form.dao.RunInTransaction(func(txDao *daos.Dao) error {
|
||||
importErr := txDao.ImportCollections(
|
||||
form.Collections,
|
||||
collections,
|
||||
form.DeleteMissing,
|
||||
form.beforeRecordsSync,
|
||||
)
|
||||
|
||||
@@ -404,16 +404,16 @@ func TestCollectionsImportSubmitInterceptors(t *testing.T) {
|
||||
testErr := errors.New("test_error")
|
||||
|
||||
interceptor1Called := false
|
||||
interceptor1 := func(next forms.InterceptorNextFunc) forms.InterceptorNextFunc {
|
||||
return func() error {
|
||||
interceptor1 := func(next forms.InterceptorNextFunc[[]*models.Collection]) forms.InterceptorNextFunc[[]*models.Collection] {
|
||||
return func(imports []*models.Collection) error {
|
||||
interceptor1Called = true
|
||||
return next()
|
||||
return next(imports)
|
||||
}
|
||||
}
|
||||
|
||||
interceptor2Called := false
|
||||
interceptor2 := func(next forms.InterceptorNextFunc) forms.InterceptorNextFunc {
|
||||
return func() error {
|
||||
interceptor2 := func(next forms.InterceptorNextFunc[[]*models.Collection]) forms.InterceptorNextFunc[[]*models.Collection] {
|
||||
return func(imports []*models.Collection) error {
|
||||
interceptor2Called = true
|
||||
return testErr
|
||||
}
|
||||
|
||||
@@ -114,9 +114,9 @@ func (form *RecordEmailChangeConfirm) parseToken(token string) (*models.Record,
|
||||
// Submit validates and submits the auth record email change confirmation form.
|
||||
// On success returns the updated auth record associated to `form.Token`.
|
||||
//
|
||||
// You can optionally provide a list of InterceptorWithRecordFunc to
|
||||
// You can optionally provide a list of InterceptorFunc to
|
||||
// further modify the form behavior before persisting it.
|
||||
func (form *RecordEmailChangeConfirm) Submit(interceptors ...InterceptorWithRecordFunc) (*models.Record, error) {
|
||||
func (form *RecordEmailChangeConfirm) Submit(interceptors ...InterceptorFunc[*models.Record]) (*models.Record, error) {
|
||||
if err := form.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -130,7 +130,8 @@ func (form *RecordEmailChangeConfirm) Submit(interceptors ...InterceptorWithReco
|
||||
authRecord.SetVerified(true)
|
||||
authRecord.RefreshTokenKey() // invalidate old tokens
|
||||
|
||||
interceptorsErr := runInterceptorsWithRecord(authRecord, func(m *models.Record) error {
|
||||
interceptorsErr := runInterceptors(authRecord, func(m *models.Record) error {
|
||||
authRecord = m
|
||||
return form.dao.SaveRecord(m)
|
||||
}, interceptors...)
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ func TestRecordEmailChangeConfirmValidateAndSubmit(t *testing.T) {
|
||||
}
|
||||
|
||||
interceptorCalls := 0
|
||||
interceptor := func(next forms.InterceptorWithRecordNextFunc) forms.InterceptorWithRecordNextFunc {
|
||||
interceptor := func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] {
|
||||
return func(r *models.Record) error {
|
||||
interceptorCalls++
|
||||
return next(r)
|
||||
@@ -165,7 +165,7 @@ func TestRecordEmailChangeConfirmInterceptors(t *testing.T) {
|
||||
testErr := errors.New("test_error")
|
||||
|
||||
interceptor1Called := false
|
||||
interceptor1 := func(next forms.InterceptorWithRecordNextFunc) forms.InterceptorWithRecordNextFunc {
|
||||
interceptor1 := func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] {
|
||||
return func(record *models.Record) error {
|
||||
interceptor1Called = true
|
||||
return next(record)
|
||||
@@ -173,7 +173,7 @@ func TestRecordEmailChangeConfirmInterceptors(t *testing.T) {
|
||||
}
|
||||
|
||||
interceptor2Called := false
|
||||
interceptor2 := func(next forms.InterceptorWithRecordNextFunc) forms.InterceptorWithRecordNextFunc {
|
||||
interceptor2 := func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] {
|
||||
return func(record *models.Record) error {
|
||||
interceptorEmail = record.Email()
|
||||
interceptor2Called = true
|
||||
|
||||
@@ -62,14 +62,14 @@ func (form *RecordEmailChangeRequest) checkUniqueEmail(value any) error {
|
||||
|
||||
// Submit validates and sends the change email request.
|
||||
//
|
||||
// You can optionally provide a list of InterceptorWithRecordFunc to
|
||||
// You can optionally provide a list of InterceptorFunc to
|
||||
// further modify the form behavior before persisting it.
|
||||
func (form *RecordEmailChangeRequest) Submit(interceptors ...InterceptorWithRecordFunc) error {
|
||||
func (form *RecordEmailChangeRequest) Submit(interceptors ...InterceptorFunc[*models.Record]) error {
|
||||
if err := form.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return runInterceptorsWithRecord(form.record, func(m *models.Record) error {
|
||||
return runInterceptors(form.record, func(m *models.Record) error {
|
||||
return mails.SendRecordChangeEmail(form.app, m, form.NewEmail)
|
||||
}, interceptors...)
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ func TestRecordEmailChangeRequestValidateAndSubmit(t *testing.T) {
|
||||
}
|
||||
|
||||
interceptorCalls := 0
|
||||
interceptor := func(next forms.InterceptorWithRecordNextFunc) forms.InterceptorWithRecordNextFunc {
|
||||
interceptor := func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] {
|
||||
return func(r *models.Record) error {
|
||||
interceptorCalls++
|
||||
return next(r)
|
||||
@@ -119,7 +119,7 @@ func TestRecordEmailChangeRequestInterceptors(t *testing.T) {
|
||||
testErr := errors.New("test_error")
|
||||
|
||||
interceptor1Called := false
|
||||
interceptor1 := func(next forms.InterceptorWithRecordNextFunc) forms.InterceptorWithRecordNextFunc {
|
||||
interceptor1 := func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] {
|
||||
return func(record *models.Record) error {
|
||||
interceptor1Called = true
|
||||
return next(record)
|
||||
@@ -127,7 +127,7 @@ func TestRecordEmailChangeRequestInterceptors(t *testing.T) {
|
||||
}
|
||||
|
||||
interceptor2Called := false
|
||||
interceptor2 := func(next forms.InterceptorWithRecordNextFunc) forms.InterceptorWithRecordNextFunc {
|
||||
interceptor2 := func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] {
|
||||
return func(record *models.Record) error {
|
||||
interceptor2Called = true
|
||||
return testErr
|
||||
|
||||
@@ -14,12 +14,25 @@ import (
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// RecordOAuth2LoginData defines the OA
|
||||
type RecordOAuth2LoginData struct {
|
||||
ExternalAuth *models.ExternalAuth
|
||||
Record *models.Record
|
||||
OAuth2User *auth.AuthUser
|
||||
}
|
||||
|
||||
// BeforeOAuth2RecordCreateFunc defines a callback function that will
|
||||
// be called before OAuth2 new Record creation.
|
||||
type BeforeOAuth2RecordCreateFunc func(createForm *RecordUpsert, authRecord *models.Record, authUser *auth.AuthUser) error
|
||||
|
||||
// RecordOAuth2Login is an auth record OAuth2 login form.
|
||||
type RecordOAuth2Login struct {
|
||||
app core.App
|
||||
dao *daos.Dao
|
||||
collection *models.Collection
|
||||
|
||||
beforeOAuth2RecordCreateFunc BeforeOAuth2RecordCreateFunc
|
||||
|
||||
// Optional auth record that will be used if no external
|
||||
// auth relation is found (if it is from the same collection)
|
||||
loggedAuthRecord *models.Record
|
||||
@@ -62,6 +75,11 @@ func (form *RecordOAuth2Login) SetDao(dao *daos.Dao) {
|
||||
form.dao = dao
|
||||
}
|
||||
|
||||
// SetBeforeNewRecordCreateFunc sets a before OAuth2 record create callback handler.
|
||||
func (form *RecordOAuth2Login) SetBeforeNewRecordCreateFunc(f BeforeOAuth2RecordCreateFunc) {
|
||||
form.beforeOAuth2RecordCreateFunc = f
|
||||
}
|
||||
|
||||
// Validate makes the form validatable by implementing [validation.Validatable] interface.
|
||||
func (form *RecordOAuth2Login) Validate() error {
|
||||
return validation.ValidateStruct(form,
|
||||
@@ -87,11 +105,14 @@ func (form *RecordOAuth2Login) checkProviderName(value any) error {
|
||||
//
|
||||
// If an auth record doesn't exist, it will make an attempt to create it
|
||||
// based on the fetched OAuth2 profile data via a local [RecordUpsert] form.
|
||||
// You can intercept/modify the create form by setting the optional beforeCreateFuncs argument.
|
||||
// You can intercept/modify the Record create form with [form.SetBeforeNewRecordCreateFunc()].
|
||||
//
|
||||
// You can also optionally provide a list of InterceptorFunc to
|
||||
// further modify the form behavior before persisting it.
|
||||
//
|
||||
// On success returns the authorized record model and the fetched provider's data.
|
||||
func (form *RecordOAuth2Login) Submit(
|
||||
beforeCreateFuncs ...func(createForm *RecordUpsert, authRecord *models.Record, authUser *auth.AuthUser) error,
|
||||
interceptors ...InterceptorFunc[*RecordOAuth2LoginData],
|
||||
) (*models.Record, *auth.AuthUser, error) {
|
||||
if err := form.Validate(); err != nil {
|
||||
return nil, nil, err
|
||||
@@ -147,16 +168,37 @@ func (form *RecordOAuth2Login) Submit(
|
||||
authRecord, _ = form.dao.FindAuthRecordByEmail(form.collection.Id, authUser.Email)
|
||||
}
|
||||
|
||||
saveErr := form.dao.RunInTransaction(func(txDao *daos.Dao) error {
|
||||
if authRecord == nil {
|
||||
authRecord = models.NewRecord(form.collection)
|
||||
authRecord.RefreshId()
|
||||
authRecord.MarkAsNew()
|
||||
createForm := NewRecordUpsert(form.app, authRecord)
|
||||
interceptorData := &RecordOAuth2LoginData{
|
||||
ExternalAuth: rel,
|
||||
Record: authRecord,
|
||||
OAuth2User: authUser,
|
||||
}
|
||||
|
||||
interceptorsErr := runInterceptors(interceptorData, func(newData *RecordOAuth2LoginData) error {
|
||||
return form.submit(newData)
|
||||
}, interceptors...)
|
||||
|
||||
if interceptorsErr != nil {
|
||||
return nil, interceptorData.OAuth2User, interceptorsErr
|
||||
}
|
||||
|
||||
return interceptorData.Record, interceptorData.OAuth2User, nil
|
||||
}
|
||||
|
||||
func (form *RecordOAuth2Login) submit(data *RecordOAuth2LoginData) error {
|
||||
return form.dao.RunInTransaction(func(txDao *daos.Dao) error {
|
||||
if data.Record == nil {
|
||||
data.Record = models.NewRecord(form.collection)
|
||||
data.Record.RefreshId()
|
||||
data.Record.MarkAsNew()
|
||||
createForm := NewRecordUpsert(form.app, data.Record)
|
||||
createForm.SetFullManageAccess(true)
|
||||
createForm.SetDao(txDao)
|
||||
if authUser.Username != "" && usernameRegex.MatchString(authUser.Username) {
|
||||
createForm.Username = form.dao.SuggestUniqueAuthRecordUsername(form.collection.Id, authUser.Username)
|
||||
if data.OAuth2User.Username != "" && usernameRegex.MatchString(data.OAuth2User.Username) {
|
||||
createForm.Username = form.dao.SuggestUniqueAuthRecordUsername(
|
||||
form.collection.Id,
|
||||
data.OAuth2User.Username,
|
||||
)
|
||||
}
|
||||
|
||||
// load custom data
|
||||
@@ -164,10 +206,10 @@ func (form *RecordOAuth2Login) Submit(
|
||||
|
||||
// load the OAuth2 profile data as fallback
|
||||
if createForm.Email == "" {
|
||||
createForm.Email = authUser.Email
|
||||
createForm.Email = data.OAuth2User.Email
|
||||
}
|
||||
createForm.Verified = false
|
||||
if createForm.Email == authUser.Email {
|
||||
if createForm.Email == data.OAuth2User.Email {
|
||||
// mark as verified as long as it matches the OAuth2 data (even if the email is empty)
|
||||
createForm.Verified = true
|
||||
}
|
||||
@@ -176,11 +218,8 @@ func (form *RecordOAuth2Login) Submit(
|
||||
createForm.PasswordConfirm = createForm.Password
|
||||
}
|
||||
|
||||
for _, f := range beforeCreateFuncs {
|
||||
if f == nil {
|
||||
continue
|
||||
}
|
||||
if err := f(createForm, authRecord, authUser); err != nil {
|
||||
if form.beforeOAuth2RecordCreateFunc != nil {
|
||||
if err := form.beforeOAuth2RecordCreateFunc(createForm, data.Record, data.OAuth2User); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -190,45 +229,39 @@ func (form *RecordOAuth2Login) Submit(
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// update the existing auth record empty email if the authUser has one
|
||||
// update the existing auth record empty email if the data.OAuth2User has one
|
||||
// (this is in case previously the auth record was created
|
||||
// with an OAuth2 provider that didn't return an email address)
|
||||
if authRecord.Email() == "" && authUser.Email != "" {
|
||||
authRecord.SetEmail(authUser.Email)
|
||||
if err := txDao.SaveRecord(authRecord); err != nil {
|
||||
if data.Record.Email() == "" && data.OAuth2User.Email != "" {
|
||||
data.Record.SetEmail(data.OAuth2User.Email)
|
||||
if err := txDao.SaveRecord(data.Record); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// update the existing auth record verified state
|
||||
// (only if the auth record doesn't have an email or the auth record email match with the one in authUser)
|
||||
if !authRecord.Verified() && (authRecord.Email() == "" || authRecord.Email() == authUser.Email) {
|
||||
authRecord.SetVerified(true)
|
||||
if err := txDao.SaveRecord(authRecord); err != nil {
|
||||
// (only if the auth record doesn't have an email or the auth record email match with the one in data.OAuth2User)
|
||||
if !data.Record.Verified() && (data.Record.Email() == "" || data.Record.Email() == data.OAuth2User.Email) {
|
||||
data.Record.SetVerified(true)
|
||||
if err := txDao.SaveRecord(data.Record); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// create ExternalAuth relation if missing
|
||||
if rel == nil {
|
||||
rel = &models.ExternalAuth{
|
||||
CollectionId: authRecord.Collection().Id,
|
||||
RecordId: authRecord.Id,
|
||||
if data.ExternalAuth == nil {
|
||||
data.ExternalAuth = &models.ExternalAuth{
|
||||
CollectionId: data.Record.Collection().Id,
|
||||
RecordId: data.Record.Id,
|
||||
Provider: form.Provider,
|
||||
ProviderId: authUser.Id,
|
||||
ProviderId: data.OAuth2User.Id,
|
||||
}
|
||||
if err := txDao.SaveExternalAuth(rel); err != nil {
|
||||
if err := txDao.SaveExternalAuth(data.ExternalAuth); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if saveErr != nil {
|
||||
return nil, authUser, saveErr
|
||||
}
|
||||
|
||||
return authRecord, authUser, nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package forms
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
|
||||
validation "github.com/go-ozzo/ozzo-validation/v4"
|
||||
@@ -48,30 +49,47 @@ func (form *RecordPasswordLogin) Validate() error {
|
||||
|
||||
// Submit validates and submits the form.
|
||||
// On success returns the authorized record model.
|
||||
func (form *RecordPasswordLogin) Submit() (*models.Record, error) {
|
||||
//
|
||||
// You can optionally provide a list of InterceptorFunc to
|
||||
// further modify the form behavior before persisting it.
|
||||
func (form *RecordPasswordLogin) Submit(interceptors ...InterceptorFunc[*models.Record]) (*models.Record, error) {
|
||||
if err := form.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authOptions := form.collection.AuthOptions()
|
||||
|
||||
if !authOptions.AllowEmailAuth && !authOptions.AllowUsernameAuth {
|
||||
return nil, errors.New("Password authentication is not allowed for the collection.")
|
||||
}
|
||||
|
||||
var record *models.Record
|
||||
var authRecord *models.Record
|
||||
var fetchErr error
|
||||
|
||||
if authOptions.AllowEmailAuth &&
|
||||
(!authOptions.AllowUsernameAuth || is.EmailFormat.Validate(form.Identity) == nil) {
|
||||
record, fetchErr = form.dao.FindAuthRecordByEmail(form.collection.Id, form.Identity)
|
||||
} else {
|
||||
record, fetchErr = form.dao.FindAuthRecordByUsername(form.collection.Id, form.Identity)
|
||||
isEmail := is.EmailFormat.Validate(form.Identity) == nil
|
||||
|
||||
if isEmail {
|
||||
if authOptions.AllowEmailAuth {
|
||||
authRecord, fetchErr = form.dao.FindAuthRecordByEmail(form.collection.Id, form.Identity)
|
||||
}
|
||||
} else if authOptions.AllowUsernameAuth {
|
||||
authRecord, fetchErr = form.dao.FindAuthRecordByUsername(form.collection.Id, form.Identity)
|
||||
}
|
||||
|
||||
if fetchErr != nil || !record.ValidatePassword(form.Password) {
|
||||
return nil, errors.New("Invalid login credentials.")
|
||||
// ignore not found errors to allow custom fetch implementations
|
||||
if fetchErr != nil && !errors.Is(fetchErr, sql.ErrNoRows) {
|
||||
return nil, fetchErr
|
||||
}
|
||||
|
||||
return record, nil
|
||||
interceptorsErr := runInterceptors(authRecord, func(m *models.Record) error {
|
||||
authRecord = m
|
||||
|
||||
if authRecord == nil || !authRecord.ValidatePassword(form.Password) {
|
||||
return errors.New("Invalid login credentials.")
|
||||
}
|
||||
|
||||
return nil
|
||||
}, interceptors...)
|
||||
|
||||
if interceptorsErr != nil {
|
||||
return nil, interceptorsErr
|
||||
}
|
||||
|
||||
return authRecord, nil
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
package forms_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/pocketbase/pocketbase/forms"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"github.com/pocketbase/pocketbase/tests"
|
||||
)
|
||||
|
||||
func TestRecordEmailLoginValidateAndSubmit(t *testing.T) {
|
||||
func TestRecordPasswordLoginValidateAndSubmit(t *testing.T) {
|
||||
testApp, _ := tests.NewTestApp()
|
||||
defer testApp.Cleanup()
|
||||
|
||||
@@ -128,3 +130,53 @@ func TestRecordEmailLoginValidateAndSubmit(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecordPasswordLoginInterceptors(t *testing.T) {
|
||||
testApp, _ := tests.NewTestApp()
|
||||
defer testApp.Cleanup()
|
||||
|
||||
authCollection, err := testApp.Dao().FindCollectionByNameOrId("users")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
form := forms.NewRecordPasswordLogin(testApp, authCollection)
|
||||
form.Identity = "test@example.com"
|
||||
form.Password = "123456"
|
||||
var interceptorRecord *models.Record
|
||||
testErr := errors.New("test_error")
|
||||
|
||||
interceptor1Called := false
|
||||
interceptor1 := func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] {
|
||||
return func(record *models.Record) error {
|
||||
interceptor1Called = true
|
||||
return next(record)
|
||||
}
|
||||
}
|
||||
|
||||
interceptor2Called := false
|
||||
interceptor2 := func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] {
|
||||
return func(record *models.Record) error {
|
||||
interceptorRecord = record
|
||||
interceptor2Called = true
|
||||
return testErr
|
||||
}
|
||||
}
|
||||
|
||||
_, submitErr := form.Submit(interceptor1, interceptor2)
|
||||
if submitErr != testErr {
|
||||
t.Fatalf("Expected submitError %v, got %v", testErr, submitErr)
|
||||
}
|
||||
|
||||
if !interceptor1Called {
|
||||
t.Fatalf("Expected interceptor1 to be called")
|
||||
}
|
||||
|
||||
if !interceptor2Called {
|
||||
t.Fatalf("Expected interceptor2 to be called")
|
||||
}
|
||||
|
||||
if interceptorRecord == nil || interceptorRecord.Email() != form.Identity {
|
||||
t.Fatalf("Expected auth Record model with email %s, got %v", form.Identity, interceptorRecord)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,9 +72,9 @@ func (form *RecordPasswordResetConfirm) checkToken(value any) error {
|
||||
// Submit validates and submits the form.
|
||||
// On success returns the updated auth record associated to `form.Token`.
|
||||
//
|
||||
// You can optionally provide a list of InterceptorWithRecordFunc to
|
||||
// further modify the form behavior before persisting it.
|
||||
func (form *RecordPasswordResetConfirm) Submit(interceptors ...InterceptorWithRecordFunc) (*models.Record, error) {
|
||||
// You can optionally provide a list of InterceptorFunc to further
|
||||
// modify the form behavior before persisting it.
|
||||
func (form *RecordPasswordResetConfirm) Submit(interceptors ...InterceptorFunc[*models.Record]) (*models.Record, error) {
|
||||
if err := form.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -91,7 +91,8 @@ func (form *RecordPasswordResetConfirm) Submit(interceptors ...InterceptorWithRe
|
||||
return nil, err
|
||||
}
|
||||
|
||||
interceptorsErr := runInterceptorsWithRecord(authRecord, func(m *models.Record) error {
|
||||
interceptorsErr := runInterceptors(authRecord, func(m *models.Record) error {
|
||||
authRecord = m
|
||||
return form.dao.SaveRecord(m)
|
||||
}, interceptors...)
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ func TestRecordPasswordResetConfirmValidateAndSubmit(t *testing.T) {
|
||||
}
|
||||
|
||||
interceptorCalls := 0
|
||||
interceptor := func(next forms.InterceptorWithRecordNextFunc) forms.InterceptorWithRecordNextFunc {
|
||||
interceptor := func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] {
|
||||
return func(r *models.Record) error {
|
||||
interceptorCalls++
|
||||
return next(r)
|
||||
@@ -157,7 +157,7 @@ func TestRecordPasswordResetConfirmInterceptors(t *testing.T) {
|
||||
testErr := errors.New("test_error")
|
||||
|
||||
interceptor1Called := false
|
||||
interceptor1 := func(next forms.InterceptorWithRecordNextFunc) forms.InterceptorWithRecordNextFunc {
|
||||
interceptor1 := func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] {
|
||||
return func(record *models.Record) error {
|
||||
interceptor1Called = true
|
||||
return next(record)
|
||||
@@ -165,7 +165,7 @@ func TestRecordPasswordResetConfirmInterceptors(t *testing.T) {
|
||||
}
|
||||
|
||||
interceptor2Called := false
|
||||
interceptor2 := func(next forms.InterceptorWithRecordNextFunc) forms.InterceptorWithRecordNextFunc {
|
||||
interceptor2 := func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] {
|
||||
return func(record *models.Record) error {
|
||||
interceptorTokenKey = record.TokenKey()
|
||||
interceptor2Called = true
|
||||
|
||||
@@ -60,9 +60,9 @@ func (form *RecordPasswordResetRequest) Validate() error {
|
||||
// Submit validates and submits the form.
|
||||
// On success, sends a password reset email to the `form.Email` auth record.
|
||||
//
|
||||
// You can optionally provide a list of InterceptorWithRecordFunc to
|
||||
// further modify the form behavior before persisting it.
|
||||
func (form *RecordPasswordResetRequest) Submit(interceptors ...InterceptorWithRecordFunc) error {
|
||||
// You can optionally provide a list of InterceptorFunc to further
|
||||
// modify the form behavior before persisting it.
|
||||
func (form *RecordPasswordResetRequest) Submit(interceptors ...InterceptorFunc[*models.Record]) error {
|
||||
if err := form.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -81,7 +81,7 @@ func (form *RecordPasswordResetRequest) Submit(interceptors ...InterceptorWithRe
|
||||
// update last sent timestamp
|
||||
authRecord.Set(schema.FieldNameLastResetSentAt, types.NowDateTime())
|
||||
|
||||
return runInterceptorsWithRecord(authRecord, func(m *models.Record) error {
|
||||
return runInterceptors(authRecord, func(m *models.Record) error {
|
||||
if err := mails.SendRecordPasswordReset(form.app, m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ func TestRecordPasswordResetRequestSubmit(t *testing.T) {
|
||||
}
|
||||
|
||||
interceptorCalls := 0
|
||||
interceptor := func(next forms.InterceptorWithRecordNextFunc) forms.InterceptorWithRecordNextFunc {
|
||||
interceptor := func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] {
|
||||
return func(r *models.Record) error {
|
||||
interceptorCalls++
|
||||
return next(r)
|
||||
@@ -135,7 +135,7 @@ func TestRecordPasswordResetRequestInterceptors(t *testing.T) {
|
||||
testErr := errors.New("test_error")
|
||||
|
||||
interceptor1Called := false
|
||||
interceptor1 := func(next forms.InterceptorWithRecordNextFunc) forms.InterceptorWithRecordNextFunc {
|
||||
interceptor1 := func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] {
|
||||
return func(record *models.Record) error {
|
||||
interceptor1Called = true
|
||||
return next(record)
|
||||
@@ -143,7 +143,7 @@ func TestRecordPasswordResetRequestInterceptors(t *testing.T) {
|
||||
}
|
||||
|
||||
interceptor2Called := false
|
||||
interceptor2 := func(next forms.InterceptorWithRecordNextFunc) forms.InterceptorWithRecordNextFunc {
|
||||
interceptor2 := func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] {
|
||||
return func(record *models.Record) error {
|
||||
interceptorLastResetSentAt = record.LastResetSentAt()
|
||||
interceptor2Called = true
|
||||
|
||||
@@ -718,12 +718,14 @@ func (form *RecordUpsert) DrySubmit(callback func(txDao *daos.Dao) error) error
|
||||
//
|
||||
// You can optionally provide a list of InterceptorFunc to further
|
||||
// modify the form behavior before persisting it.
|
||||
func (form *RecordUpsert) Submit(interceptors ...InterceptorFunc) error {
|
||||
func (form *RecordUpsert) Submit(interceptors ...InterceptorFunc[*models.Record]) error {
|
||||
if err := form.ValidateAndFill(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return runInterceptors(func() error {
|
||||
return runInterceptors(form.record, func(record *models.Record) error {
|
||||
form.record = record
|
||||
|
||||
if !form.record.HasId() {
|
||||
form.record.RefreshId()
|
||||
form.record.MarkAsNew()
|
||||
|
||||
+11
-11
@@ -428,10 +428,10 @@ func TestRecordUpsertSubmitFailure(t *testing.T) {
|
||||
form.LoadRequest(req, "")
|
||||
|
||||
interceptorCalls := 0
|
||||
interceptor := func(next forms.InterceptorNextFunc) forms.InterceptorNextFunc {
|
||||
return func() error {
|
||||
interceptor := func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] {
|
||||
return func(r *models.Record) error {
|
||||
interceptorCalls++
|
||||
return next()
|
||||
return next(r)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -505,10 +505,10 @@ func TestRecordUpsertSubmitSuccess(t *testing.T) {
|
||||
form.LoadRequest(req, "")
|
||||
|
||||
interceptorCalls := 0
|
||||
interceptor := func(next forms.InterceptorNextFunc) forms.InterceptorNextFunc {
|
||||
return func() error {
|
||||
interceptor := func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] {
|
||||
return func(r *models.Record) error {
|
||||
interceptorCalls++
|
||||
return next()
|
||||
return next(r)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -566,16 +566,16 @@ func TestRecordUpsertSubmitInterceptors(t *testing.T) {
|
||||
interceptorRecordTitle := ""
|
||||
|
||||
interceptor1Called := false
|
||||
interceptor1 := func(next forms.InterceptorNextFunc) forms.InterceptorNextFunc {
|
||||
return func() error {
|
||||
interceptor1 := func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] {
|
||||
return func(r *models.Record) error {
|
||||
interceptor1Called = true
|
||||
return next()
|
||||
return next(r)
|
||||
}
|
||||
}
|
||||
|
||||
interceptor2Called := false
|
||||
interceptor2 := func(next forms.InterceptorNextFunc) forms.InterceptorNextFunc {
|
||||
return func() error {
|
||||
interceptor2 := func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] {
|
||||
return func(r *models.Record) error {
|
||||
interceptorRecordTitle = record.GetString("title") // to check if the record was filled
|
||||
interceptor2Called = true
|
||||
return testErr
|
||||
|
||||
@@ -77,9 +77,9 @@ func (form *RecordVerificationConfirm) checkToken(value any) error {
|
||||
// Submit validates and submits the form.
|
||||
// On success returns the verified auth record associated to `form.Token`.
|
||||
//
|
||||
// You can optionally provide a list of InterceptorWithRecordFunc to
|
||||
// further modify the form behavior before persisting it.
|
||||
func (form *RecordVerificationConfirm) Submit(interceptors ...InterceptorWithRecordFunc) (*models.Record, error) {
|
||||
// You can optionally provide a list of InterceptorFunc to further
|
||||
// modify the form behavior before persisting it.
|
||||
func (form *RecordVerificationConfirm) Submit(interceptors ...InterceptorFunc[*models.Record]) (*models.Record, error) {
|
||||
if err := form.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -98,7 +98,9 @@ func (form *RecordVerificationConfirm) Submit(interceptors ...InterceptorWithRec
|
||||
record.SetVerified(true)
|
||||
}
|
||||
|
||||
interceptorsErr := runInterceptorsWithRecord(record, func(m *models.Record) error {
|
||||
interceptorsErr := runInterceptors(record, func(m *models.Record) error {
|
||||
record = m
|
||||
|
||||
if wasVerified {
|
||||
return nil // already verified
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ func TestRecordVerificationConfirmValidateAndSubmit(t *testing.T) {
|
||||
}
|
||||
|
||||
interceptorCalls := 0
|
||||
interceptor := func(next forms.InterceptorWithRecordNextFunc) forms.InterceptorWithRecordNextFunc {
|
||||
interceptor := func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] {
|
||||
return func(r *models.Record) error {
|
||||
interceptorCalls++
|
||||
return next(r)
|
||||
@@ -117,7 +117,7 @@ func TestRecordVerificationConfirmInterceptors(t *testing.T) {
|
||||
testErr := errors.New("test_error")
|
||||
|
||||
interceptor1Called := false
|
||||
interceptor1 := func(next forms.InterceptorWithRecordNextFunc) forms.InterceptorWithRecordNextFunc {
|
||||
interceptor1 := func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] {
|
||||
return func(record *models.Record) error {
|
||||
interceptor1Called = true
|
||||
return next(record)
|
||||
@@ -125,7 +125,7 @@ func TestRecordVerificationConfirmInterceptors(t *testing.T) {
|
||||
}
|
||||
|
||||
interceptor2Called := false
|
||||
interceptor2 := func(next forms.InterceptorWithRecordNextFunc) forms.InterceptorWithRecordNextFunc {
|
||||
interceptor2 := func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] {
|
||||
return func(record *models.Record) error {
|
||||
interceptorVerified = record.Verified()
|
||||
interceptor2Called = true
|
||||
|
||||
@@ -60,9 +60,9 @@ func (form *RecordVerificationRequest) Validate() error {
|
||||
// Submit validates and sends a verification request email
|
||||
// to the `form.Email` auth record.
|
||||
//
|
||||
// You can optionally provide a list of InterceptorWithRecordFunc to
|
||||
// further modify the form behavior before persisting it.
|
||||
func (form *RecordVerificationRequest) Submit(interceptors ...InterceptorWithRecordFunc) error {
|
||||
// You can optionally provide a list of InterceptorFunc to further
|
||||
// modify the form behavior before persisting it.
|
||||
func (form *RecordVerificationRequest) Submit(interceptors ...InterceptorFunc[*models.Record]) error {
|
||||
if err := form.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -87,7 +87,7 @@ func (form *RecordVerificationRequest) Submit(interceptors ...InterceptorWithRec
|
||||
record.SetLastVerificationSentAt(types.NowDateTime())
|
||||
}
|
||||
|
||||
return runInterceptorsWithRecord(record, func(m *models.Record) error {
|
||||
return runInterceptors(record, func(m *models.Record) error {
|
||||
if m.Verified() {
|
||||
return nil // already verified
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ func TestRecordVerificationRequestSubmit(t *testing.T) {
|
||||
}
|
||||
|
||||
interceptorCalls := 0
|
||||
interceptor := func(next forms.InterceptorWithRecordNextFunc) forms.InterceptorWithRecordNextFunc {
|
||||
interceptor := func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] {
|
||||
return func(r *models.Record) error {
|
||||
interceptorCalls++
|
||||
return next(r)
|
||||
@@ -153,7 +153,7 @@ func TestRecordVerificationRequestInterceptors(t *testing.T) {
|
||||
testErr := errors.New("test_error")
|
||||
|
||||
interceptor1Called := false
|
||||
interceptor1 := func(next forms.InterceptorWithRecordNextFunc) forms.InterceptorWithRecordNextFunc {
|
||||
interceptor1 := func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] {
|
||||
return func(record *models.Record) error {
|
||||
interceptor1Called = true
|
||||
return next(record)
|
||||
@@ -161,7 +161,7 @@ func TestRecordVerificationRequestInterceptors(t *testing.T) {
|
||||
}
|
||||
|
||||
interceptor2Called := false
|
||||
interceptor2 := func(next forms.InterceptorWithRecordNextFunc) forms.InterceptorWithRecordNextFunc {
|
||||
interceptor2 := func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] {
|
||||
return func(record *models.Record) error {
|
||||
interceptorLastVerificationSentAt = record.LastVerificationSentAt()
|
||||
interceptor2Called = true
|
||||
|
||||
@@ -50,12 +50,14 @@ func (form *SettingsUpsert) Validate() error {
|
||||
//
|
||||
// You can optionally provide a list of InterceptorFunc to further
|
||||
// modify the form behavior before persisting it.
|
||||
func (form *SettingsUpsert) Submit(interceptors ...InterceptorFunc) error {
|
||||
func (form *SettingsUpsert) Submit(interceptors ...InterceptorFunc[*settings.Settings]) error {
|
||||
if err := form.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return runInterceptors(func() error {
|
||||
return runInterceptors(form.Settings, func(s *settings.Settings) error {
|
||||
form.Settings = s
|
||||
|
||||
encryptionKey := os.Getenv(form.app.EncryptionEnv())
|
||||
if err := form.dao.SaveSettings(form.Settings, encryptionKey); err != nil {
|
||||
return err
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
validation "github.com/go-ozzo/ozzo-validation/v4"
|
||||
"github.com/pocketbase/pocketbase/forms"
|
||||
"github.com/pocketbase/pocketbase/models/settings"
|
||||
"github.com/pocketbase/pocketbase/tests"
|
||||
"github.com/pocketbase/pocketbase/tools/security"
|
||||
)
|
||||
@@ -78,10 +79,10 @@ func TestSettingsUpsertValidateAndSubmit(t *testing.T) {
|
||||
}
|
||||
|
||||
interceptorCalls := 0
|
||||
interceptor := func(next forms.InterceptorNextFunc) forms.InterceptorNextFunc {
|
||||
return func() error {
|
||||
interceptor := func(next forms.InterceptorNextFunc[*settings.Settings]) forms.InterceptorNextFunc[*settings.Settings] {
|
||||
return func(s *settings.Settings) error {
|
||||
interceptorCalls++
|
||||
return next()
|
||||
return next(s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,16 +136,16 @@ func TestSettingsUpsertSubmitInterceptors(t *testing.T) {
|
||||
testErr := errors.New("test_error")
|
||||
|
||||
interceptor1Called := false
|
||||
interceptor1 := func(next forms.InterceptorNextFunc) forms.InterceptorNextFunc {
|
||||
return func() error {
|
||||
interceptor1 := func(next forms.InterceptorNextFunc[*settings.Settings]) forms.InterceptorNextFunc[*settings.Settings] {
|
||||
return func(s *settings.Settings) error {
|
||||
interceptor1Called = true
|
||||
return next()
|
||||
return next(s)
|
||||
}
|
||||
}
|
||||
|
||||
interceptor2Called := false
|
||||
interceptor2 := func(next forms.InterceptorNextFunc) forms.InterceptorNextFunc {
|
||||
return func() error {
|
||||
interceptor2 := func(next forms.InterceptorNextFunc[*settings.Settings]) forms.InterceptorNextFunc[*settings.Settings] {
|
||||
return func(s *settings.Settings) error {
|
||||
interceptor2Called = true
|
||||
return testErr
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ func UniqueId(dao *daos.Dao, tableName string) validation.RuleFunc {
|
||||
Limit(1).
|
||||
Row(&foundId)
|
||||
|
||||
if !errors.Is(err, sql.ErrNoRows) || foundId != "" {
|
||||
if (err != nil && !errors.Is(err, sql.ErrNoRows)) || foundId != "" {
|
||||
return validation.NewError("validation_invalid_id", "The model id is invalid or already exists.")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user