// Package indexes declares the MongoDB indexes more than one Vantage service // depends on. package indexes import ( "context" "errors" "fmt" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" ) // legacyUserEmailIndex is the global unique index on users.email that this // package used to declare. It is dropped on sight. const legacyUserEmailIndex = "email_1" // indexNotFound is MongoDB's IndexNotFound error code. Two services booting at // once can both decide to drop the legacy index; the loser must not treat that // as a failure. const indexNotFound = 27 // EnsureCoreIndexes declares the unique indexes on users and instances. // // users is unique on (instance_id, email), NOT on email alone. One address is // one user WITHIN an instance; the same address may hold a user in several // instances, because an account's people are projected into each instance they // are granted access to. // // This is a security property, not an optimisation, and it is only sufficient // because every lookup by email is scoped by instance. There is deliberately no // unscoped lookup by email anywhere in the codebase: an unscoped FindOne would // return an arbitrary one of several matching users, which on the login path // means signing someone into a tenant that is not theirs. If you are about to // add one, you are about to reintroduce that bug. // // Creating an index that already exists with the same specification is a no-op, // so this is safe to call at every boot from every service. func EnsureCoreIndexes(ctx context.Context, db *mongo.Database) error { // Create the replacement BEFORE dropping the legacy index. A failure here // leaves the old constraint in place, which is safe; a failure after the // drop would leave the collection unconstrained, which is not. if _, err := db.Collection("users").Indexes().CreateOne(ctx, mongo.IndexModel{ Keys: bson.D{{Key: "instance_id", Value: 1}, {Key: "email", Value: 1}}, Options: options.Index().SetUnique(true).SetName("instance_email_unique"), }); err != nil { return fmt.Errorf("users.instance_id+email index: %w", err) } if err := dropIndexIfExists(ctx, db.Collection("users"), legacyUserEmailIndex); err != nil { return fmt.Errorf("drop users.%s: %w", legacyUserEmailIndex, err) } if _, err := db.Collection("instances").Indexes().CreateOne(ctx, mongo.IndexModel{ Keys: bson.D{{Key: "slug", Value: 1}}, Options: options.Index().SetUnique(true), }); err != nil { return fmt.Errorf("instances.slug index: %w", err) } return nil } // dropIndexIfExists drops name, treating "it was not there" as success whether // that is discovered by listing or by racing another service to the drop. func dropIndexIfExists(ctx context.Context, col *mongo.Collection, name string) error { cur, err := col.Indexes().List(ctx) if err != nil { return err } var existing []struct { Name string `bson:"name"` } if err := cur.All(ctx, &existing); err != nil { return err } found := false for _, i := range existing { if i.Name == name { found = true break } } if !found { return nil } err = col.Indexes().DropOne(ctx, name) if err == nil { return nil } var srvErr mongo.ServerError if errors.As(err, &srvErr) && srvErr.HasErrorCode(indexNotFound) { return nil } return err }