package apis import ( "database/sql" "errors" validation "github.com/go-ozzo/ozzo-validation/v4" "github.com/go-ozzo/ozzo-validation/v4/is" "github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/tools/list" ) func recordAuthWithPassword(e *core.RequestEvent) error { collection, err := findAuthCollection(e) if err != nil { return err } if !collection.PasswordAuth.Enabled { return e.ForbiddenError("The collection is not configured to allow password authentication.", nil) } form := &authWithPasswordForm{} if err = e.BindBody(form); err != nil { return firstApiError(err, e.BadRequestError("An error occurred while loading the submitted data.", err)) } if err = form.validate(collection); err != nil { return firstApiError(err, e.BadRequestError("An error occurred while validating the submitted data.", err)) } var foundRecord *core.Record var foundErr error if form.IdentityField != "" { foundRecord, foundErr = e.App.FindFirstRecordByData(collection.Id, form.IdentityField, form.Identity) } else { // prioritize email lookup isEmail := is.EmailFormat.Validate(form.Identity) == nil if isEmail && list.ExistInSlice(core.FieldNameEmail, collection.PasswordAuth.IdentityFields) { foundRecord, foundErr = e.App.FindAuthRecordByEmail(collection.Id, form.Identity) } // search by the other identity fields if !isEmail || foundErr != nil { for _, name := range collection.PasswordAuth.IdentityFields { if !isEmail && name == core.FieldNameEmail { continue // no need to search by the email field if it is not an email } foundRecord, foundErr = e.App.FindFirstRecordByData(collection.Id, name, form.Identity) if foundErr == nil { break } } } } // ignore not found errors to allow custom record find implementations if foundErr != nil && !errors.Is(foundErr, sql.ErrNoRows) { return e.InternalServerError("", foundErr) } event := new(core.RecordAuthWithPasswordRequestEvent) event.RequestEvent = e event.Collection = collection event.Record = foundRecord event.Identity = form.Identity event.Password = form.Password event.IdentityField = form.IdentityField return e.App.OnRecordAuthWithPasswordRequest().Trigger(event, func(e *core.RecordAuthWithPasswordRequestEvent) error { if e.Record == nil || !e.Record.ValidatePassword(e.Password) { return e.BadRequestError("Failed to authenticate.", errors.New("invalid login credentials")) } return RecordAuthResponse(e.RequestEvent, e.Record, core.MFAMethodPassword, nil) }) } // ------------------------------------------------------------------- type authWithPasswordForm struct { Identity string `form:"identity" json:"identity"` Password string `form:"password" json:"password"` // IdentityField specifies the field to use to search for the identity // (leave it empty for "auto" detection). IdentityField string `form:"identityField" json:"identityField"` } func (form *authWithPasswordForm) validate(collection *core.Collection) error { return validation.ValidateStruct(form, validation.Field(&form.Identity, validation.Required, validation.Length(1, 255)), validation.Field(&form.Password, validation.Required, validation.Length(1, 255)), validation.Field(&form.IdentityField, validation.In(list.ToInterfaceSlice(collection.PasswordAuth.IdentityFields)...)), ) }