[#1240] added dedicated before/after auth hooks and refactored the submit interceptors

This commit is contained in:
Gani Georgiev
2023-01-15 17:00:28 +02:00
parent 8f6f87902a
commit 36ab3fd162
46 changed files with 1125 additions and 295 deletions
+23 -7
View File
@@ -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
}
+47
View File
@@ -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)
}
}
+11 -3
View File
@@ -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
+71 -1
View File
@@ -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")
}
}
+12 -6
View File
@@ -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...)
}
+69 -1
View File
@@ -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")
}
}
+3 -3
View File
@@ -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...)
}
+8 -8
View File
@@ -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
View File
@@ -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)
}
+3 -3
View File
@@ -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...)
}
+8 -8
View File
@@ -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
+3 -3
View File
@@ -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,
)
+5 -5
View File
@@ -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
}
+4 -3
View File
@@ -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...)
+3 -3
View File
@@ -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
+3 -3
View File
@@ -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...)
}
+3 -3
View File
@@ -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
+70 -37
View File
@@ -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
}
+32 -14
View File
@@ -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
}
+53 -1
View File
@@ -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)
}
}
+5 -4
View File
@@ -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...)
+3 -3
View File
@@ -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
+4 -4
View File
@@ -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
}
+3 -3
View File
@@ -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
+4 -2
View File
@@ -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
View File
@@ -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
+6 -4
View File
@@ -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
}
+3 -3
View File
@@ -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
+4 -4
View File
@@ -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
}
+3 -3
View File
@@ -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
+4 -2
View File
@@ -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
+9 -8
View File
@@ -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
}
+1 -1
View File
@@ -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.")
}