[#6337] added support for case-insensitive password auth

This commit is contained in:
Gani Georgiev
2025-01-26 12:24:16 +02:00
parent c101798516
commit 33340a6977
13 changed files with 412 additions and 39 deletions
+2 -2
View File
@@ -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),
+2 -2
View File
@@ -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}}".`,
+2 -1
View File
@@ -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
View File
@@ -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 {
+1 -1
View File
@@ -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
+33 -9
View File
@@ -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())
}
})