[#6337] added support for case-insensitive password auth
This commit is contained in:
@@ -989,7 +989,7 @@ func (c *Collection) initTokenKeyField() {
|
||||
}
|
||||
|
||||
// ensure that there is a unique index for the field
|
||||
if !dbutils.HasSingleColumnUniqueIndex(FieldNameTokenKey, c.Indexes) {
|
||||
if _, ok := dbutils.FindSingleColumnUniqueIndex(c.Indexes, FieldNameTokenKey); !ok {
|
||||
c.Indexes = append(c.Indexes, fmt.Sprintf(
|
||||
"CREATE UNIQUE INDEX `%s` ON `%s` (`%s`)",
|
||||
c.fieldIndexName(FieldNameTokenKey),
|
||||
@@ -1015,7 +1015,7 @@ func (c *Collection) initEmailField() {
|
||||
}
|
||||
|
||||
// ensure that there is a unique index for the email field
|
||||
if !dbutils.HasSingleColumnUniqueIndex(FieldNameEmail, c.Indexes) {
|
||||
if _, ok := dbutils.FindSingleColumnUniqueIndex(c.Indexes, FieldNameEmail); !ok {
|
||||
c.Indexes = append(c.Indexes, fmt.Sprintf(
|
||||
"CREATE UNIQUE INDEX `%s` ON `%s` (`%s`) WHERE `%s` != ''",
|
||||
c.fieldIndexName(FieldNameEmail),
|
||||
|
||||
@@ -456,7 +456,7 @@ func (cv *collectionValidator) checkFieldsForUniqueIndex(value any) error {
|
||||
SetParams(map[string]any{"fieldName": name})
|
||||
}
|
||||
|
||||
if !dbutils.HasSingleColumnUniqueIndex(name, cv.new.Indexes) {
|
||||
if _, ok := dbutils.FindSingleColumnUniqueIndex(cv.new.Indexes, name); !ok {
|
||||
return validation.NewError("validation_missing_unique_constraint", "The field {{.fieldName}} doesn't have a UNIQUE constraint.").
|
||||
SetParams(map[string]any{"fieldName": name})
|
||||
}
|
||||
@@ -666,7 +666,7 @@ func (cv *collectionValidator) checkIndexes(value any) error {
|
||||
if cv.new.IsAuth() {
|
||||
requiredNames := []string{FieldNameTokenKey, FieldNameEmail}
|
||||
for _, name := range requiredNames {
|
||||
if !dbutils.HasSingleColumnUniqueIndex(name, indexes) {
|
||||
if _, ok := dbutils.FindSingleColumnUniqueIndex(indexes, name); !ok {
|
||||
return validation.NewError(
|
||||
"validation_missing_required_unique_index",
|
||||
`Missing required unique index for field "{{.fieldName}}".`,
|
||||
|
||||
@@ -505,7 +505,8 @@ func (r *runner) processActiveProps() (*search.ResolverResult, error) {
|
||||
isBackRelMultiple := backRelField.IsMultiple()
|
||||
if !isBackRelMultiple {
|
||||
// additionally check if the rel field has a single column unique index
|
||||
isBackRelMultiple = !dbutils.HasSingleColumnUniqueIndex(backRelField.Name, backCollection.Indexes)
|
||||
_, hasUniqueIndex := dbutils.FindSingleColumnUniqueIndex(backCollection.Indexes, backRelField.Name)
|
||||
isBackRelMultiple = !hasUniqueIndex
|
||||
}
|
||||
|
||||
if !isBackRelMultiple {
|
||||
|
||||
+16
-1
@@ -9,6 +9,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/tools/dbutils"
|
||||
"github.com/pocketbase/pocketbase/tools/inflector"
|
||||
"github.com/pocketbase/pocketbase/tools/list"
|
||||
"github.com/pocketbase/pocketbase/tools/search"
|
||||
@@ -527,20 +528,34 @@ func (app *BaseApp) FindAuthRecordByToken(token string, validTypes ...string) (*
|
||||
|
||||
// FindAuthRecordByEmail finds the auth record associated with the provided email.
|
||||
//
|
||||
// The email check would be case-insensitive if the related collection
|
||||
// email unique index has COLLATE NOCASE specified for the email column.
|
||||
//
|
||||
// Returns an error if it is not an auth collection or the record is not found.
|
||||
func (app *BaseApp) FindAuthRecordByEmail(collectionModelOrIdentifier any, email string) (*Record, error) {
|
||||
collection, err := getCollectionByModelOrIdentifier(app, collectionModelOrIdentifier)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch auth collection: %w", err)
|
||||
}
|
||||
|
||||
if !collection.IsAuth() {
|
||||
return nil, fmt.Errorf("%q is not an auth collection", collection.Name)
|
||||
}
|
||||
|
||||
record := &Record{}
|
||||
|
||||
var expr dbx.Expression
|
||||
|
||||
index, ok := dbutils.FindSingleColumnUniqueIndex(collection.Indexes, FieldNameEmail)
|
||||
if ok && strings.EqualFold(index.Columns[0].Collate, "nocase") {
|
||||
// case-insensitive search
|
||||
expr = dbx.NewExp("[["+FieldNameEmail+"]] = {:email} COLLATE NOCASE", dbx.Params{"email": email})
|
||||
} else {
|
||||
expr = dbx.HashExp{FieldNameEmail: email}
|
||||
}
|
||||
|
||||
err = app.RecordQuery(collection).
|
||||
AndWhere(dbx.HashExp{FieldNameEmail: email}).
|
||||
AndWhere(expr).
|
||||
Limit(1).
|
||||
One(record)
|
||||
if err != nil {
|
||||
|
||||
@@ -143,7 +143,7 @@ func (app *BaseApp) expandRecords(records []*Record, expandPath string, fetchFun
|
||||
MaxSelect: 2147483647,
|
||||
CollectionId: indirectRel.Id,
|
||||
}
|
||||
if dbutils.HasSingleColumnUniqueIndex(indirectRelField.GetName(), indirectRel.Indexes) {
|
||||
if _, ok := dbutils.FindSingleColumnUniqueIndex(indirectRel.Indexes, indirectRelField.GetName()); ok {
|
||||
relField.MaxSelect = 1
|
||||
}
|
||||
relCollection = indirectRel
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/pocketbase/pocketbase/tests"
|
||||
"github.com/pocketbase/pocketbase/tools/dbutils"
|
||||
"github.com/pocketbase/pocketbase/tools/types"
|
||||
)
|
||||
|
||||
@@ -966,23 +967,46 @@ func TestFindAuthRecordByToken(t *testing.T) {
|
||||
func TestFindAuthRecordByEmail(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
scenarios := []struct {
|
||||
collectionIdOrName string
|
||||
email string
|
||||
nocaseIndex bool
|
||||
expectError bool
|
||||
}{
|
||||
{"missing", "test@example.com", true},
|
||||
{"demo2", "test@example.com", true},
|
||||
{"users", "missing@example.com", true},
|
||||
{"users", "test@example.com", false},
|
||||
{"clients", "test2@example.com", false},
|
||||
{"missing", "test@example.com", false, true},
|
||||
{"demo2", "test@example.com", false, true},
|
||||
{"users", "missing@example.com", false, true},
|
||||
{"users", "test@example.com", false, false},
|
||||
{"clients", "test2@example.com", false, false},
|
||||
// case-insensitive tests
|
||||
{"clients", "TeSt2@example.com", false, true},
|
||||
{"clients", "TeSt2@example.com", true, false},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(fmt.Sprintf("%s_%s", s.collectionIdOrName, s.email), func(t *testing.T) {
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
collection, _ := app.FindCollectionByNameOrId(s.collectionIdOrName)
|
||||
if collection != nil {
|
||||
emailIndex, ok := dbutils.FindSingleColumnUniqueIndex(collection.Indexes, core.FieldNameEmail)
|
||||
if ok {
|
||||
if s.nocaseIndex {
|
||||
emailIndex.Columns[0].Collate = "nocase"
|
||||
} else {
|
||||
emailIndex.Columns[0].Collate = ""
|
||||
}
|
||||
|
||||
collection.RemoveIndex(emailIndex.IndexName)
|
||||
collection.Indexes = append(collection.Indexes, emailIndex.Build())
|
||||
err := app.Save(collection)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to update email index: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
record, err := app.FindAuthRecordByEmail(s.collectionIdOrName, s.email)
|
||||
|
||||
hasErr := err != nil
|
||||
@@ -994,7 +1018,7 @@ func TestFindAuthRecordByEmail(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
if record.Email() != s.email {
|
||||
if !strings.EqualFold(record.Email(), s.email) {
|
||||
t.Fatalf("Expected record with email %s, got %s", s.email, record.Email())
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user